From 110fcd3b840ebb80292a0472acc74147af8efc31 Mon Sep 17 00:00:00 2001 From: fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:58:46 +0200 Subject: [PATCH 001/337] Initial commit --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..55b93b4d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 fiddy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From a496e55817575a3c821980e69c3379a2d67fd967 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:12:42 +0200 Subject: [PATCH 002/337] init --- .github/workflows/.pre-commit-config.yaml | 11 + .github/workflows/unit-tests.yaml | 30 + .gitignore | 153 ++ .pre-commit-config.yaml | 26 + .python-version | 1 + LICENSE | 2 +- contracts/CurveStableSwap2NG.vy | 1578 ++++++++++++++++++ contracts/CurveStableSwapFactoryNG.vy | 912 ++++++++++ contracts/CurveStableSwapNGFactoryHandler.vy | 542 ++++++ requirements.txt | 23 + 10 files changed, 3277 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/.pre-commit-config.yaml create mode 100644 .github/workflows/unit-tests.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .python-version create mode 100644 contracts/CurveStableSwap2NG.vy create mode 100644 contracts/CurveStableSwapFactoryNG.vy create mode 100644 contracts/CurveStableSwapNGFactoryHandler.vy create mode 100644 requirements.txt diff --git a/.github/workflows/.pre-commit-config.yaml b/.github/workflows/.pre-commit-config.yaml new file mode 100644 index 00000000..81bd81e9 --- /dev/null +++ b/.github/workflows/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +name: pre-commit + +on: [pull_request, push] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml new file mode 100644 index 00000000..9bcecfc5 --- /dev/null +++ b/.github/workflows/unit-tests.yaml @@ -0,0 +1,30 @@ +name: unit-tests-boa + +on: ["push", "pull_request"] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + boa-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Cache Compiler Installations + uses: actions/cache@v2 + with: + path: | + ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v2 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: pip install -r requirements.txt + + - name: Run Tests + run: python -m pytest tests/boa -n auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..107b9cc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,153 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +**/.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +profile-venv/ +profilingenv/ +ENV/ +env.bak/ +venv.bak/ +apeenv/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# eth-brownie/ape +.history +.build +reports/ + +# pycharm/vscode +.DS_Store +.idea +**/.idea +.vscode +*.csv + +# misc +/data +todo.txt +node_modules +*.json +/audits + +# temp +docs/ +scripts/experiments/get_p.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..11b0062b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + args: [--line-length=79] + - repo: https://github.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black", --line-length=79] + +default_language_version: + python: python3.10.4 diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..8d7f852b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.4 diff --git a/LICENSE b/LICENSE index 55b93b4d..e6a181f7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 fiddy +Copyright (c) 2022 fiddy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/contracts/CurveStableSwap2NG.vy b/contracts/CurveStableSwap2NG.vy new file mode 100644 index 00000000..490433c0 --- /dev/null +++ b/contracts/CurveStableSwap2NG.vy @@ -0,0 +1,1578 @@ +# @version 0.3.9 +""" +@title CurveStableSwap2NG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice 2 coin pool implementation with no lending, i.e. tokens are not + deposited into lending markets +@dev ERC20 support for return True/revert, return True/False, return None + ERC20 tokens can have arbitrary decimals (<=18). + Additional features include: + 1. Support for positive-rebasing and fee-on-transfer tokens + 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + 3. Support for ETH/WETH transfers + 4. Adds oracles for coin[1] w.r.t coin[0] + 5. Adds exchanging tokens with callbacks that allows for: + a. reduced ERC20 token transfers in zap contracts + b. swaps without transferFrom (no need for token approvals) +""" + +from vyper.interfaces import ERC20 + +implements: ERC20 + +# ------------------------------- Interfaces --------------------------------- + +interface Factory: + def convert_fees() -> bool: nonpayable + def get_fee_receiver(_pool: address) -> address: view + def admin() -> address: view + +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + +interface WETH: + def deposit(): payable + def withdraw(_amount: uint256): nonpayable + +# --------------------------------- Events ----------------------------------- + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + +event TokenExchange: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event AddLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event RemoveLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + token_supply: uint256 + +event RemoveLiquidityOne: + provider: indexed(address) + token_id: int128 + token_amount: uint256 + coin_amount: uint256 + token_supply: uint256 + +event RemoveLiquidityImbalance: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event RampA: + old_A: uint256 + new_A: uint256 + initial_time: uint256 + future_time: uint256 + +event StopRampA: + A: uint256 + t: uint256 + +event CommitNewFee: + new_fee: uint256 + +event ApplyNewFee: + fee: uint256 + +# ---------------------------- Storage Variables ----------------------------- + +WETH20: public(immutable(address)) + + +N_COINS: constant(uint256) = 2 +N_COINS_128: constant(int128) = 2 +PRECISION: constant(uint256) = 10 ** 18 +ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 + +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +ADMIN_FEE: constant(uint256) = 5000000000 + +A_PRECISION: constant(uint256) = 100 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 +MAX_A: constant(uint256) = 10 ** 6 +MAX_A_CHANGE: constant(uint256) = 10 +MIN_RAMP_TIME: constant(uint256) = 86400 + +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +PERMIT_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 +VERSION: constant(String[8]) = "v7.0.0" + +# shift(2**32 - 1, 224) +ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 + + +factory: public(address) + +coins: public(address[N_COINS]) +admin_balances: public(uint256[N_COINS]) +fee: public(uint256) # fee * 1e10 +future_fee: public(uint256) +admin_action_deadline: public(uint256) + +initial_A: public(uint256) +future_A: public(uint256) +initial_A_time: public(uint256) +future_A_time: public(uint256) + +rate_multipliers: uint256[N_COINS] +# [bytes4 method_id][bytes8 ][bytes20 oracle] +oracles: uint256[N_COINS] + +name: public(String[64]) +symbol: public(String[32]) + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) + +DOMAIN_SEPARATOR: public(bytes32) +nonces: public(HashMap[address, uint256]) + +last_prices_packed: uint256 # [last_price, ma_price] +ma_exp_time: public(uint256) +ma_last_time: public(uint256) + + +# ------------------------------ AMM Setup ----------------------------------- + + +@external +def __init__(_weth: address): + + WETH20 = _weth + + # we do this to prevent the implementation contract from being used as a pool + self.factory = 0x0000000000000000000000000000000000000001 + assert N_COINS == 2 + + +@external +def initialize( + _name: String[32], + _symbol: String[10], + _coins: address[4], + _rate_multipliers: uint256[4], + _A: uint256, + _fee: uint256, + _weth: address, + _ma_exp_time: uint256, + _method_ids: bytes4[4], + _oracles: address[4] +): + """ + @notice Initialize the pool contract + @param _name Name of the new plain pool + @param _symbol Symbol for the new plain pool - will be + concatenated with factory symbol + @param _coins List of addresses of the coins being used in the pool. + @param _A Amplification co-efficient - a lower value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + """ + + # check if factory was already set to prevent initializing contract twice + assert self.factory == empty(address) + + for i in range(N_COINS): + + coin: address = _coins[i] + if coin == empty(address): + break + + self.coins[i] = coin + self.rate_multipliers[i] = _rate_multipliers[i] + self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + + A: uint256 = _A * A_PRECISION + self.initial_A = A + self.future_A = A + self.fee = _fee + self.factory = msg.sender + + assert _ma_exp_time != 0 + self.ma_exp_time = _ma_exp_time + self.last_prices_packed = self.pack_prices(10**18, 10**18) + self.ma_last_time = block.timestamp + + name: String[64] = concat("Curve.fi Factory Plain Pool: ", _name) + self.name = name + self.symbol = concat(_symbol, "-f") + + self.DOMAIN_SEPARATOR = keccak256( + _abi_encode(EIP712_TYPEHASH, keccak256(name), keccak256(VERSION), chain.id, self) + ) + + # fire a transfer event so block explorers identify the contract as an ERC20 + log Transfer(empty(address), self, 0) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert _ma_exp_time != 0 + + self.ma_exp_time = _ma_exp_time + + +# ------------------ Token transfers in and out of the AMM ------------------- + +@payable +@external +def __default__(): + if msg.value > 0: + assert WETH20 in self.coins + + +@internal +def _transfer_in( + coin: address, + dx: uint256, + dy: uint256, + mvalue: uint256, + callbacker: address, + callback_sig: bytes32, + sender: address, + receiver: address, + use_eth: bool, +) -> uint256: + """ + @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig` + if it is not empty. + @dev The callback sig must have the following args: + sender: address + receiver: address + coin: address + dx: uint256 + dy: uint256 + The `dy` that the pool enforces is actually min_dy. + Callback only occurs for `exchange_extended`. + Callback cannot happen for `_use_eth` = True. + @dev If callback_sig is empty, `_transfer_in` does a transferFrom. + @params _coin address of the coin to transfer in. + @params dx amount of `_coin` to transfer into the pool. + @params dy amount of `_coin` to transfer out of the pool. + @params mvalue msg.value if the transfer is ETH, 0 otherwise. + @params callbacker address to call `callback_sig` on. + @params callback_sig signature of the callback function. + @params sender address to transfer `_coin` from. + @params receiver address to transfer `_coin` to. + @params use_eth True if the transfer is ETH, False otherwise. + """ + _dx: uint256 = dx + + if use_eth and coin == WETH20: # <----------- Pool receives native token. + + assert mvalue == _dx # dev: incorrect eth amount + + else: # <------- Pool receives wrapped native token and not native token. + + assert mvalue == 0 # dev: nonzero eth amount + + initial_x: uint256 = ERC20(coin).balanceOf(self) + + # --------------------- Start Callback Handling ---------------------- + + if callback_sig == empty(bytes32): + + assert ERC20(coin).transferFrom( + sender, self, _dx, default_return_value=True + ) + + else: + + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, coin, _dx, dy) + ) + ) + + # If the coin is a fee-on-transfer token, transferring `_dx` amount can + # result in the pool receiving slightly less amount. So: recalculate dx + + _dx = ERC20(coin).balanceOf(self) - initial_x + + assert _dx > 0 # dev: pool received 0 tokens + + # -------------------- End Callback Handling ------------------------- + + if coin == WETH20: + WETH(WETH20).withdraw(_dx) # <--------- if WETH was transferred in + # previous step and `not use_eth`, withdraw WETH to ETH. + + # Return _dx so it can be used by `_exchange` and `add_liquidity`. + return _dx + + +@internal +def _transfer_out( + _coin: address, _amount: uint256, use_eth: bool, receiver: address +): + """ + @notice Transfer a single token from the pool to receiver. + @dev This function is called by `remove_liquidity` and + `remove_liquidity_one` and `_exchange` methods. + @params _coin Address of the token to transfer out + @params _amount Amount of token to transfer out + @params use_eth Whether to transfer ETH or not + @params receiver Address to send the tokens to + """ + + if use_eth and _coin == WETH20: + raw_call(receiver, b"", value=_amount) + else: + if _coin == WETH20: + WETH(WETH20).deposit(value=_amount) + + assert ERC20(_coin).transfer( + receiver, _amount, default_return_value=True + ) + + +# -------------------------- AMM Main Functions ------------------------------ + + +@payable +@external +@nonreentrant('lock') +def exchange( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + Allows for native token swaps (e.g. ETH <> whatever) + If native token is not in coin list and msg.value > 0, swap will revert + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + return self._exchange( + msg.sender, + msg.value, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32) + ) + + +@external +@nonreentrant('lock') +def exchange_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _sender: address, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + Not payable (does not accept eth) + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: No callback specified + return self._exchange( + _sender, + 0, # mvalue is zero here + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + msg.sender, # <---------------------------- callbacker is msg.sender. + _cb + ) + + +@payable +@external +@nonreentrant('lock') +def add_liquidity( + _amounts: uint256[N_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + coins: address[N_COINS] = self.coins + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + + for i in range(N_COINS): + + if _amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + msg.value, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + _use_eth + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + False + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[_receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), _receiver, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount + + +@external +@nonreentrant('lock') +def remove_liquidity_one_coin( + _burn_amount: uint256, + i: int128, + _min_received: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Withdraw a single coin from the pool + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_received Minimum amount of coin to receive + @param _receiver Address that receives the withdrawn coins + @return Amount of coin received + """ + dy: uint256[3] = self._calc_withdraw_one_coin(_burn_amount, i) + assert dy[0] >= _min_received, "Not enough coins removed" + + self.admin_balances[i] += dy[1] * ADMIN_FEE / FEE_DENOMINATOR + total_supply: uint256 = self.totalSupply - _burn_amount + self.totalSupply = total_supply + self.balanceOf[msg.sender] -= _burn_amount + + log Transfer(msg.sender, empty(address), _burn_amount) + + self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) + + self.save_p_from_price(dy[2]) + + return dy[0] + + +@external +@nonreentrant('lock') +def remove_liquidity_imbalance( + _amounts: uint256[N_COINS], + _max_burn_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the withdrawn coins + @return Actual amount of the LP token burned in the withdrawal + """ + amp: uint256 = self._A() + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + coins: address[N_COINS] = self.coins + + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + amount: uint256 = _amounts[i] + if amount != 0: + new_balances[i] -= amount + + self._transfer_out(coins[i], amount, _use_eth, _receiver) + + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + D2: uint256 = self.get_D_mem(rates, new_balances, amp) + + self.save_p(new_balances, amp, D2) + + total_supply: uint256 = self.totalSupply + burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 + assert burn_amount > 1 # dev: zero tokens burned + assert burn_amount <= _max_burn_amount, "Slippage screwed you" + + total_supply -= burn_amount + self.totalSupply = total_supply + self.balanceOf[msg.sender] -= burn_amount + log Transfer(msg.sender, empty(address), burn_amount) + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + + return burn_amount + + +@external +@nonreentrant('lock') +def remove_liquidity( + _burn_amount: uint256, + _min_amounts: uint256[N_COINS], + _use_eth: bool = False, + _receiver: address = msg.sender, + _claim_admin_fees: bool = True, +) -> uint256[N_COINS]: + """ + @notice Withdraw coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the withdrawn coins + @return List of amounts of coins that were withdrawn + """ + total_supply: uint256 = self.totalSupply + amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + balances: uint256[N_COINS] = self._balances() + coins: address[N_COINS] = self.coins + + for i in range(N_COINS): + value: uint256 = balances[i] * _burn_amount / total_supply + assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" + amounts[i] = value + + self._transfer_out(coins[i], value, _use_eth, _receiver) + + total_supply -= _burn_amount + self.balanceOf[msg.sender] -= _burn_amount + self.totalSupply = total_supply + log Transfer(msg.sender, empty(address), _burn_amount) + + log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply) + + # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. + if _claim_admin_fees: + self._withdraw_admin_fees() + + return amounts + + +@external +def withdraw_admin_fees(): + """ + @notice Claim admin fees. Callable by anyone. + """ + self._withdraw_admin_fees() + + +# ------------------------ AMM Internal Functions ---------------------------- + + +@internal +def _exchange( + sender: address, + mvalue: uint256, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + use_eth: bool, + receiver: address, + callbacker: address, + callback_sig: bytes32 +) -> uint256: + + assert i != j # dev: coin index out of range + assert _dx > 0 # dev: do not exchange 0 coins + + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) + coins: address[N_COINS] = self.coins + + # --------------------------- Do Transfer in ----------------------------- + + dx: uint256 = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) + + # ------------------------------------------------------------------------ + + x: uint256 = xp[i] + dx * rates[i] / PRECISION + + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(i, j, x, xp, amp, D) + + dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" + + self.admin_balances[j] += ( + dy_fee * ADMIN_FEE / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # xp is not used anymore, so we reuse it for price calc + xp[i] = x + xp[j] = y + # D is not changed because we did not apply a fee + self.save_p(xp, amp, D) + + # --------------------------- Do Transfer out ---------------------------- + + self._transfer_out(coins[j], dy, use_eth, receiver) + + # ------------------------------------------------------------------------ + + log TokenExchange(msg.sender, i, _dx, j, dy) # <--- TODO: which dx in the event? + + return dy + + +@view +@internal +def _stored_rates() -> uint256[N_COINS]: + + rates: uint256[N_COINS] = self.rate_multipliers + + for i in range(N_COINS): + oracle: uint256 = self.oracles[i] + if oracle == 0: + continue + + # NOTE: assumed that response is of precision 10**18 + response: Bytes[32] = raw_call( + convert(oracle % 2**160, address), + _abi_encode(oracle & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ) + + assert len(response) != 0 + rates[1] = rates[1] * convert(response, uint256) / PRECISION + + return rates + + +@view +@internal +def _balances() -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + return result + + +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@pure +@internal +def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = _rates[i] * _balances[i] / PRECISION + return result + + +@pure +@internal +def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for i in range(255): + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + Dprev: uint256 = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@view +@internal +def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: + xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + +@view +@internal +def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, _D: uint256) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + amp: uint256 = _amp + D: uint256 = _D + if _D == 0: + amp = self._A() + D = self.get_D(xp, amp) + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(N_COINS_128): + if _i == i: + _x = x + elif _i != j: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@pure +@internal +def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(N_COINS_128): + if _i != i: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + amp: uint256 = self._A() + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + D0: uint256 = self.get_D(xp, amp) + + total_supply: uint256 = self.totalSupply + D1: uint256 = D0 - _burn_amount * D0 / total_supply + new_y: uint256 = self.get_y_D(amp, i, xp, D1) + + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: uint256[N_COINS] = empty(uint256[N_COINS]) + + for j in range(N_COINS_128): + dx_expected: uint256 = 0 + xp_j: uint256 = xp[j] + if j == i: + dx_expected = xp_j * D1 / D0 - new_y + else: + dx_expected = xp_j - xp_j * D1 / D0 + xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) + dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees + dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + + xp[i] = new_y + last_p: uint256 = 0 + if new_y > 0: + last_p = self._get_p(xp, amp, D1) + + return [dy, dy_0 - dy, last_p] + + +@internal +def _withdraw_admin_fees(): + receiver: address = Factory(self.factory).get_fee_receiver(self) + + for i in range(N_COINS): + amount: uint256 = self.admin_balances[i] + + if amount > 0: + + if self.coins[i] == WETH20: + raw_call(receiver, b"", value=amount) + else: + assert ERC20(self.coins[i]).transfer( + receiver, + amount, + default_return_value=True + ) + + self.admin_balances = empty(uint256[N_COINS]) + + +# -------------------------- AMM Price Methods ------------------------------- + + +@pure +@internal +def pack_prices(p1: uint256, p2: uint256) -> uint256: + assert p1 < 2**128 + assert p2 < 2**128 + return p1 | (p2 << 128) + + +@internal +@view +def _get_p(xp: uint256[N_COINS], amp: uint256, D: uint256) -> uint256: + # dx_0 / dx_1 only, however can have any number of coins in pool + ANN: uint256 = amp * N_COINS + Dr: uint256 = D / (N_COINS**N_COINS) + for i in range(N_COINS): + Dr = Dr * D / xp[i] + return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) + + +@internal +def save_p_from_price(last_price: uint256): + """ + Saves current price and its EMA + """ + if last_price != 0: + self.last_prices_packed = self.pack_prices(last_price, self._ma_price()) + if self.ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp + + +@internal +def save_p(xp: uint256[N_COINS], amp: uint256, D: uint256): + """ + Saves current price and its EMA + """ + self.save_p_from_price(self._get_p(xp, amp, D)) + + +@internal +@view +def _ma_price() -> uint256: + ma_last_time: uint256 = self.ma_last_time + + pp: uint256 = self.last_prices_packed + last_price: uint256 = pp & (2**128 - 1) + last_ema_price: uint256 = (pp >> 128) + + if ma_last_time < block.timestamp: + alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) + return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 + + else: + return last_ema_price + + +# ----------------------------- Math Utils ----------------------------------- + + +@internal +@pure +def exp(x: int256) -> uint256: + + """ + @dev Calculates the natural exponential function of a signed integer with + a precision of 1e18. + @notice Note that this function consumes about 810 gas units. The implementation + is inspired by Remco Bloemen's implementation under the MIT license here: + https://xn--2-umb.com/22/exp-ln. + @dev This implementation is derived from Snekmate, which is authored + by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. + https://github.com/pcaversaccio/snekmate + @param x The 32-byte variable. + @return int256 The 32-byte calculation result. + """ + value: int256 = x + + # If the result is `< 0.5`, we return zero. This happens when we have the following: + # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". + if (x <= -42139678854452767551): + return empty(uint256) + + # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. + # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". + assert x < 135305999368893231589, "wad_exp overflow" + + # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher + # intermediate precision and a binary base. This base conversion is a multiplication with + # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". + value = unsafe_div(x << 78, 5 ** 18) + + # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two + # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives + # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". + k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 + value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) + + # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, + # we will multiply by a scaling factor later. + y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) + p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ + 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) + + # We leave `p` in the "2 ** 192" base so that we do not have to scale it up + # again for the division. + q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) + q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) + q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) + + # The polynomial `q` has no zeros in the range because all its roots are complex. + # No scaling is required, as `p` is already "2 ** 96" too large. Also, + # `r` is in the range "(0.09, 0.25) * 2**96" after the division. + r: int256 = unsafe_div(p, q) + + # To finalise the calculation, we have to multiply `r` by: + # - the scale factor "s = ~6.031367120", + # - the factor "2 ** k" from the range reduction, and + # - the factor "1e18 / 2 ** 96" for the base conversion. + # We do this all at once, with an intermediate result in "2**213" base, + # so that the final right shift always gives a positive value. + + # Note that to circumvent Vyper's safecast feature for the potentially + # negative parameter value `r`, we first convert `r` to `bytes32` and + # subsequently to `uint256`. Remember that the EVM default behaviour is + # to use two's complement representation to handle signed integers. + return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) + + +# --------------------------- ERC20 Functionality ---------------------------- + + +@view +@external +def decimals() -> uint8: + """ + @notice Get the number of decimals for this token + @dev Implemented as a view method to reduce gas costs + @return uint8 decimal places + """ + return 18 + + +@internal +def _transfer(_from: address, _to: address, _value: uint256): + # # NOTE: vyper does not allow underflows + # # so the following subtraction would revert on insufficient balance + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + + log Transfer(_from, _to, _value) + + +@external +def transfer(_to : address, _value : uint256) -> bool: + """ + @dev Transfer token for a specified address + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + self._transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from : address, _to : address, _value : uint256) -> bool: + """ + @dev Transfer tokens from one address to another. + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + self._transfer(_from, _to, _value) + + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != max_value(uint256): + self.allowance[_from][msg.sender] = _allowance - _value + + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk that + someone may use both the old and new allowance by unfortunate transaction + ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + + log Approval(msg.sender, _spender, _value) + return True + + +@external +def permit( + _owner: address, + _spender: address, + _value: uint256, + _deadline: uint256, + _v: uint8, + _r: bytes32, + _s: bytes32 +) -> bool: + """ + @notice Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 + @dev Supports smart contract wallets which implement ERC1271 + https://eips.ethereum.org/EIPS/eip-1271 + @param _owner The address which is a source of funds and has signed the Permit. + @param _spender The address which is allowed to spend the funds. + @param _value The amount of tokens to be spent. + @param _deadline The timestamp after which the Permit is no longer valid. + @param _v The bytes[64] of the valid secp256k1 signature of permit by owner + @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner + @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner + @return True, if transaction completes successfully + """ + assert _owner != empty(address) + assert block.timestamp <= _deadline + + nonce: uint256 = self.nonces[_owner] + digest: bytes32 = keccak256( + concat( + b"\x19\x01", + self.DOMAIN_SEPARATOR, + keccak256(_abi_encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) + ) + ) + + if _owner.is_contract: + sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) + # reentrancy not a concern since this is a staticcall + assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL + else: + assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner + + self.allowance[_owner][_spender] = _value + self.nonces[_owner] = nonce + 1 + + log Approval(_owner, _spender, _value) + return True + + +# ------------------------- AMM View Functions ------------------------------- + + +@view +@external +def last_price() -> uint256: + return self.last_prices_packed & (2**128 - 1) + + +@view +@external +def ema_price() -> uint256: + return (self.last_prices_packed >> 128) + + +@external +@view +def get_p() -> uint256: + amp: uint256 = self._A() + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = self.get_D(xp, amp) + return self._get_p(xp, amp, D) + + +@external +@view +@nonreentrant('lock') +def price_oracle() -> uint256: + return self._ma_price() + + +@view +@external +def get_dx(i: int128, j: int128, dy: uint256) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + + y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee) + x: uint256 = self.get_y(j, i, y, xp, 0, 0) + return (x - xp[i]) * PRECISION / rates[i] + + +@view +@external +def get_dy(i: int128, j: int128, dx: uint256) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + + x: uint256 = xp[i] + (dx * rates[i] / PRECISION) + y: uint256 = self.get_y(i, j, x, xp, 0, 0) + dy: uint256 = xp[j] - y - 1 + fee: uint256 = self.fee * dy / FEE_DENOMINATOR + return (dy - fee) * PRECISION / rates[j] + + +@view +@external +def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing a single coin + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @return Amount of coin received + """ + return self._calc_withdraw_one_coin(_burn_amount, i)[0] + + +@view +@external +@nonreentrant('lock') +def get_virtual_price() -> uint256: + """ + @notice The current virtual price of the pool LP token + @dev Useful for calculating profits + @return LP token virtual price normalized to 1e18 + """ + amp: uint256 = self._A() + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = self.get_D(xp, amp) + # D is in the units similar to DAI (e.g. converted to precision 1e18) + # When balanced, D = n * x_u - total virtual value of the portfolio + return D * PRECISION / self.totalSupply + + +@view +@external +def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + amount: uint256 = _amounts[i] + if _is_deposit: + new_balances[i] += amount + else: + new_balances[i] -= amount + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + D2: uint256 = D1 + if total_supply > 0: + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2 = self.get_D(xp, amp) + else: + return D1 # Take the dust if there was any + + diff: uint256 = 0 + if _is_deposit: + diff = D2 - D0 + else: + diff = D0 - D2 + return diff * total_supply / D0 + + +@view +@external +def admin_fee() -> uint256: + return ADMIN_FEE + + +@view +@external +def A() -> uint256: + return self._A() / A_PRECISION + + +@view +@external +def A_precise() -> uint256: + return self._A() + + +@view +@external +def balances(i: uint256) -> uint256: + """ + @notice Get the current balance of a coin within the + pool, less the accrued admin fees + @param i Index value for the coin to query balance of + @return Token balance + """ + return self._balances()[i] + + +@view +@external +def get_balances() -> uint256[N_COINS]: + return self._balances() + + +@pure +@external +def version() -> String[8]: + """ + @notice Get the version of this token contract + """ + return VERSION + + +@view +@external +def oracle(_idx: uint256) -> address: + if _idx < N_COINS: + return convert(self.oracles[_idx] % 2**160, address) + return empty(address) + + +# --------------------------- AMM Admin Functions ---------------------------- + + +@external +def ramp_A(_future_A: uint256, _future_time: uint256): + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME + assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time + + _initial_A: uint256 = self._A() + _future_A_p: uint256 = _future_A * A_PRECISION + + assert _future_A > 0 and _future_A < MAX_A + if _future_A_p < _initial_A: + assert _future_A_p * MAX_A_CHANGE >= _initial_A + else: + assert _future_A_p <= _initial_A * MAX_A_CHANGE + + self.initial_A = _initial_A + self.future_A = _future_A_p + self.initial_A_time = block.timestamp + self.future_A_time = _future_time + + log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) + + +@external +def stop_ramp_A(): + assert msg.sender == Factory(self.factory).admin() # dev: only owner + + current_A: uint256 = self._A() + self.initial_A = current_A + self.future_A = current_A + self.initial_A_time = block.timestamp + self.future_A_time = block.timestamp + # now (block.timestamp < t1) is always False, so we return saved A + + log StopRampA(current_A, block.timestamp) + + +@external +def commit_new_fee(_new_fee: uint256): + assert msg.sender == Factory(self.factory).admin() + assert _new_fee <= MAX_FEE + assert self.admin_action_deadline == 0 + + self.future_fee = _new_fee + self.admin_action_deadline = block.timestamp + ADMIN_ACTIONS_DEADLINE_DT + log CommitNewFee(_new_fee) + + +@external +def apply_new_fee(): + assert msg.sender == Factory(self.factory).admin() + deadline: uint256 = self.admin_action_deadline + assert deadline != 0 and block.timestamp >= deadline + + fee: uint256 = self.future_fee + self.fee = fee + self.admin_action_deadline = 0 + log ApplyNewFee(fee) diff --git a/contracts/CurveStableSwapFactoryNG.vy b/contracts/CurveStableSwapFactoryNG.vy new file mode 100644 index 00000000..391c4c24 --- /dev/null +++ b/contracts/CurveStableSwapFactoryNG.vy @@ -0,0 +1,912 @@ +# @version 0.3.9 +""" +@title CurveStableswapFactory +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2023 - all rights reserved +@notice Permissionless pool deployer and registry +""" + +struct PoolArray: + base_pool: address + implementation: address + liquidity_gauge: address + coins: address[MAX_PLAIN_COINS] + decimals: uint256[MAX_PLAIN_COINS] + n_coins: uint256 + asset_type: uint256 + +struct BasePoolArray: + implementations: address[10] + lp_token: address + fee_receiver: address + coins: address[MAX_COINS] + decimals: uint256 + n_coins: uint256 + asset_type: uint256 + + +interface AddressProvider: + def admin() -> address: view + def get_registry() -> address: view + +interface Registry: + def get_lp_token(pool: address) -> address: view + def get_n_coins(pool: address) -> uint256: view + def get_coins(pool: address) -> address[MAX_COINS]: view + def get_pool_from_lp_token(lp_token: address) -> address: view + +interface ERC20: + def balanceOf(_addr: address) -> uint256: view + def decimals() -> uint256: view + def totalSupply() -> uint256: view + def approve(_spender: address, _amount: uint256): nonpayable + +interface CurvePlainPool: + def initialize( + _name: String[32], + _symbol: String[10], + _coins: address[4], + _rate_multipliers: uint256[4], + _A: uint256, + _fee: uint256, + _weth: address, + _ma_exp_time: uint256, + _method_ids: bytes4[4], + _oracles: address[4] + ): nonpayable + +interface CurvePool: + def A() -> uint256: view + def fee() -> uint256: view + def admin_fee() -> uint256: view + def balances(i: uint256) -> uint256: view + def admin_balances(i: uint256) -> uint256: view + def get_virtual_price() -> uint256: view + def initialize( + _name: String[32], + _symbol: String[10], + _coin: address, + _rate_multiplier: uint256, + _A: uint256, + _fee: uint256, + ): nonpayable + def exchange( + i: int128, + j: int128, + dx: uint256, + min_dy: uint256, + _receiver: address, + ) -> uint256: nonpayable + +interface CurveFactoryMetapool: + def coins(i :uint256) -> address: view + def decimals() -> uint256: view + +interface OldFactory: + def get_coins(_pool: address) -> address[2]: view + +interface LiquidityGauge: + def initialize(_lp_token: address): nonpayable + + +event BasePoolAdded: + base_pool: address + +event PlainPoolDeployed: + coins: address[MAX_PLAIN_COINS] + A: uint256 + fee: uint256 + deployer: address + +event MetaPoolDeployed: + coin: address + base_pool: address + A: uint256 + fee: uint256 + deployer: address + +event LiquidityGaugeDeployed: + pool: address + gauge: address + +WETH20: public(immutable(address)) + +MAX_COINS: constant(uint256) = 8 +MAX_PLAIN_COINS: constant(uint256) = 4 # max coins in a plain pool +ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 +OLD_FACTORY: constant(address) = 0x0959158b6040D32d04c301A72CBFD6b39E21c9AE + +admin: public(address) +future_admin: public(address) +manager: public(address) + +pool_list: public(address[4294967296]) # master list of pools +pool_count: public(uint256) # actual length of pool_list +pool_data: HashMap[address, PoolArray] + +base_pool_list: public(address[4294967296]) # master list of pools +base_pool_count: public(uint256) # actual length of pool_list +base_pool_data: HashMap[address, BasePoolArray] + +# asset -> is used in a metapool? +base_pool_assets: public(HashMap[address, bool]) + +# number of coins -> implementation addresses +# for "plain pools" (as opposed to metapools), implementation contracts +# are organized according to the number of coins in the pool +plain_implementations: public(HashMap[uint256, address[10]]) + +# fee receiver for plain pools +fee_receiver: address + +gauge_implementation: public(address) + +# mapping of coins -> pools for trading +# a mapping key is generated for each pair of addresses via +# `bitwise_xor(convert(a, uint256), convert(b, uint256))` +markets: HashMap[uint256, address[4294967296]] +market_counts: HashMap[uint256, uint256] + + +@external +def __init__(_fee_receiver: address, _weth: address): + + WETH20 = _weth + + self.admin = msg.sender + self.manager = msg.sender + self.fee_receiver = _fee_receiver + + +# <--- Factory Getters ---> + +@view +@external +def metapool_implementations(_base_pool: address) -> address[10]: + """ + @notice Get a list of implementation contracts for metapools targetting the given base pool + @dev A base pool is the pool for the LP token contained within the metapool + @param _base_pool Address of the base pool + @return List of implementation contract addresses + """ + return self.base_pool_data[_base_pool].implementations + + +@view +@external +def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address: + """ + @notice Find an available pool for exchanging two coins + @param _from Address of coin to be sent + @param _to Address of coin to be received + @param i Index value. When multiple pools are available + this value is used to return the n'th address. + @return Pool address + """ + key: uint256 = (convert(_from, uint256) ^ convert(_to, uint256)) + return self.markets[key][i] + + +# <--- Pool Getters ---> + +@view +@external +def get_base_pool(_pool: address) -> address: + """ + @notice Get the base pool for a given factory metapool + @param _pool Metapool address + @return Address of base pool + """ + return self.pool_data[_pool].base_pool + + +@view +@external +def get_n_coins(_pool: address) -> (uint256): + """ + @notice Get the number of coins in a pool + @param _pool Pool address + @return Number of coins + """ + return self.pool_data[_pool].n_coins + + +@view +@external +def get_meta_n_coins(_pool: address) -> (uint256, uint256): + """ + @notice Get the number of coins in a metapool + @param _pool Pool address + @return Number of wrapped coins, number of underlying coins + """ + base_pool: address = self.pool_data[_pool].base_pool + return 2, self.base_pool_data[base_pool].n_coins + 1 + + +@view +@external +def get_coins(_pool: address) -> address[MAX_PLAIN_COINS]: + """ + @notice Get the coins within a pool + @param _pool Pool address + @return List of coin addresses + """ + return self.pool_data[_pool].coins + + +@view +@external +def get_underlying_coins(_pool: address) -> address[MAX_COINS]: + """ + @notice Get the underlying coins within a pool + @dev Reverts if a pool does not exist or is not a metapool + @param _pool Pool address + @return List of coin addresses + """ + coins: address[MAX_COINS] = empty(address[MAX_COINS]) + base_pool: address = self.pool_data[_pool].base_pool + assert base_pool != empty(address) # dev: pool is not metapool + coins[0] = self.pool_data[_pool].coins[0] + for i in range(1, MAX_COINS): + coins[i] = self.base_pool_data[base_pool].coins[i - 1] + if coins[i] == empty(address): + break + + return coins + + +@view +@external +def get_decimals(_pool: address) -> uint256[MAX_PLAIN_COINS]: + """ + @notice Get decimal places for each coin within a pool + @param _pool Pool address + @return uint256 list of decimals + """ + if self.pool_data[_pool].base_pool != empty(address): + decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + decimals = self.pool_data[_pool].decimals + decimals[1] = 18 + return decimals + return self.pool_data[_pool].decimals + + +@view +@external +def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: + """ + @notice Get decimal places for each underlying coin within a pool + @param _pool Pool address + @return uint256 list of decimals + """ + # decimals are tightly packed as a series of uint8 within a little-endian bytes32 + # the packed value is stored as uint256 to simplify unpacking via shift and modulo + pool_decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + pool_decimals = self.pool_data[_pool].decimals + decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + decimals[0] = pool_decimals[0] + base_pool: address = self.pool_data[_pool].base_pool + packed_decimals: uint256 = self.base_pool_data[base_pool].decimals + + for i in range(MAX_COINS): + + unpacked: uint256 = (packed_decimals >> 8 * i) % 256 + if unpacked == 0: + break + decimals[i+1] = unpacked + + return decimals + + +@view +@external +def get_metapool_rates(_pool: address) -> uint256[2]: + """ + @notice Get rates for coins within a metapool + @param _pool Pool address + @return Rates for each coin, precision normalized to 10**18 + """ + rates: uint256[2] = [10**18, 0] + rates[1] = CurvePool(self.pool_data[_pool].base_pool).get_virtual_price() + return rates + + +@view +@external +def get_balances(_pool: address) -> uint256[MAX_PLAIN_COINS]: + """ + @notice Get balances for each coin within a pool + @dev For pools using lending, these are the wrapped coin balances + @param _pool Pool address + @return uint256 list of balances + """ + if self.pool_data[_pool].base_pool != empty(address): + return [CurvePool(_pool).balances(0), CurvePool(_pool).balances(1), 0, 0] + n_coins: uint256 = self.pool_data[_pool].n_coins + balances: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + for i in range(MAX_PLAIN_COINS): + if i < n_coins: + balances[i] = CurvePool(_pool).balances(i) + else: + balances[i] = 0 + return balances + + +@view +@external +def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: + """ + @notice Get balances for each underlying coin within a metapool + @param _pool Metapool address + @return uint256 list of underlying balances + """ + + underlying_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + underlying_balances[0] = CurvePool(_pool).balances(0) + + base_total_supply: uint256 = ERC20(self.pool_data[_pool].coins[1]).totalSupply() + if base_total_supply > 0: + underlying_pct: uint256 = CurvePool(_pool).balances(1) * 10**36 / base_total_supply + base_pool: address = self.pool_data[_pool].base_pool + assert base_pool != empty(address) # dev: pool is not a metapool + n_coins: uint256 = self.base_pool_data[base_pool].n_coins + for i in range(MAX_COINS): + if i == n_coins: + break + underlying_balances[i + 1] = CurvePool(base_pool).balances(i) * underlying_pct / 10**36 + + return underlying_balances + + +@view +@external +def get_A(_pool: address) -> uint256: + """ + @notice Get the amplfication co-efficient for a pool + @param _pool Pool address + @return uint256 A + """ + return CurvePool(_pool).A() + + +@view +@external +def get_fees(_pool: address) -> (uint256, uint256): + """ + @notice Get the fees for a pool + @dev Fees are expressed as integers + @return Pool fee and admin fee as uint256 with 1e10 precision + """ + return CurvePool(_pool).fee(), CurvePool(_pool).admin_fee() + + +@view +@external +def get_admin_balances(_pool: address) -> uint256[MAX_PLAIN_COINS]: + """ + @notice Get the current admin balances (uncollected fees) for a pool + @param _pool Pool address + @return List of uint256 admin balances + """ + n_coins: uint256 = self.pool_data[_pool].n_coins + admin_balances: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + for i in range(MAX_PLAIN_COINS): + if i == n_coins: + break + admin_balances[i] = CurvePool(_pool).admin_balances(i) + return admin_balances + + +@view +@external +def get_coin_indices( + _pool: address, + _from: address, + _to: address +) -> (int128, int128, bool): + """ + @notice Convert coin addresses to indices for use with pool methods + @param _pool Pool address + @param _from Coin address to be used as `i` within a pool + @param _to Coin address to be used as `j` within a pool + @return int128 `i`, int128 `j`, boolean indicating if `i` and `j` are underlying coins + """ + coin: address = self.pool_data[_pool].coins[0] + base_pool: address = self.pool_data[_pool].base_pool + if coin in [_from, _to] and base_pool != empty(address): + base_lp_token: address = self.pool_data[_pool].coins[1] + if base_lp_token in [_from, _to]: + # True and False convert to 1 and 0 - a bit of voodoo that + # works because we only ever have 2 non-underlying coins if base pool is empty(address) + return convert(_to == coin, int128), convert(_from == coin, int128), False + + found_market: bool = False + i: uint256 = 0 + j: uint256 = 0 + for x in range(MAX_COINS): + if base_pool == empty(address): + if x >= MAX_PLAIN_COINS: + raise "No available market" + if x != 0: + coin = self.pool_data[_pool].coins[x] + else: + if x != 0: + coin = self.base_pool_data[base_pool].coins[x-1] + if coin == empty(address): + raise "No available market" + if coin == _from: + i = x + elif coin == _to: + j = x + else: + continue + if found_market: + # the second time we find a match, break out of the loop + break + # the first time we find a match, set `found_market` to True + found_market = True + + return convert(i, int128), convert(j, int128), True + + +@view +@external +def get_gauge(_pool: address) -> address: + """ + @notice Get the address of the liquidity gauge contract for a factory pool + @dev Returns `empty(address)` if a gauge has not been deployed + @param _pool Pool address + @return Implementation contract address + """ + return self.pool_data[_pool].liquidity_gauge + + +@view +@external +def get_implementation_address(_pool: address) -> address: + """ + @notice Get the address of the implementation contract used for a factory pool + @param _pool Pool address + @return Implementation contract address + """ + return self.pool_data[_pool].implementation + + +@view +@external +def is_meta(_pool: address) -> bool: + """ + @notice Verify `_pool` is a metapool + @param _pool Pool address + @return True if `_pool` is a metapool + """ + return self.pool_data[_pool].base_pool != empty(address) + + +@view +@external +def get_pool_asset_type(_pool: address) -> uint256: + """ + @notice Query the asset type of `_pool` + @dev 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _pool Pool Address + @return Integer indicating the pool asset type + """ + base_pool: address = self.pool_data[_pool].base_pool + if base_pool == empty(address): + return self.pool_data[_pool].asset_type + else: + return self.base_pool_data[base_pool].asset_type + + +@view +@external +def get_fee_receiver(_pool: address) -> address: + base_pool: address = self.pool_data[_pool].base_pool + if base_pool == empty(address): + return self.fee_receiver + else: + return self.base_pool_data[base_pool].fee_receiver + + +# <--- Pool Deployers ---> + +@external +def deploy_plain_pool( + _name: String[32], + _symbol: String[10], + _coins: address[MAX_PLAIN_COINS], + _A: uint256, + _fee: uint256, + _ma_exp_time: uint256, + _method_ids: bytes4[4] = empty(bytes4[4]), + _oracles: address[4] = empty(address[4]), + _asset_type: uint256 = 0, + _implementation_idx: uint256 = 0, +) -> address: + """ + @notice Deploy a new plain pool + @param _name Name of the new plain pool + @param _symbol Symbol for the new plain pool - will be + concatenated with factory symbol + @param _coins List of addresses of the coins being used in the pool. + @param _A Amplification co-efficient - a lower value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + @param _asset_type Asset type for pool, as an integer + 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _implementation_idx Index of the implementation to use. All possible + implementations for a pool of N_COINS can be publicly accessed + via `plain_implementations(N_COINS)` + @return Address of the deployed pool + """ + assert _fee <= 100000000, "Invalid fee" + + n_coins: uint256 = MAX_PLAIN_COINS + rate_multipliers: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + + for i in range(MAX_PLAIN_COINS): + + coin: address = _coins[i] + + if coin == empty(address): + assert i > 1, "Insufficient coins" + n_coins = i + break + + decimals[i] = ERC20(coin).decimals() + assert decimals[i] < 19, "Max 18 decimals for coins" + + rate_multipliers[i] = 10 ** (36 - decimals[i]) + + for x in range(i, i+MAX_PLAIN_COINS): + if x+1 == MAX_PLAIN_COINS: + break + if _coins[x+1] == empty(address): + break + assert coin != _coins[x+1], "Duplicate coins" + + implementation: address = self.plain_implementations[n_coins][_implementation_idx] + assert implementation != empty(address), "Invalid implementation index" + pool: address = create_minimal_proxy_to(implementation) + + CurvePlainPool(pool).initialize( + _name, + _symbol, + _coins, + rate_multipliers, + _A, + _fee, + WETH20, + _ma_exp_time, + _method_ids, + _oracles + ) + + length: uint256 = self.pool_count + self.pool_list[length] = pool + self.pool_count = length + 1 + self.pool_data[pool].decimals = decimals + self.pool_data[pool].n_coins = n_coins + self.pool_data[pool].base_pool = empty(address) + self.pool_data[pool].implementation = implementation + if _asset_type != 0: + self.pool_data[pool].asset_type = _asset_type + + for i in range(MAX_PLAIN_COINS): + coin: address = _coins[i] + if coin == empty(address): + break + self.pool_data[pool].coins[i] = coin + raw_call( + coin, + concat( + method_id("approve(address,uint256)"), + convert(pool, bytes32), + convert(max_value(uint256), bytes32) + ) + ) + for j in range(MAX_PLAIN_COINS): + if i < j: + swappable_coin: address = _coins[j] + key: uint256 = (convert(coin, uint256) ^ convert(swappable_coin, uint256)) + length = self.market_counts[key] + self.markets[key][length] = pool + self.market_counts[key] = length + 1 + + log PlainPoolDeployed(_coins, _A, _fee, msg.sender) + return pool + + +@external +def deploy_metapool( + _base_pool: address, + _name: String[32], + _symbol: String[10], + _coin: address, + _A: uint256, + _fee: uint256, + _implementation_idx: uint256 = 0, +) -> address: + """ + @notice Deploy a new metapool + @param _base_pool Address of the base pool to use + within the metapool + @param _name Name of the new metapool + @param _symbol Symbol for the new metapool - will be + concatenated with the base pool symbol + @param _coin Address of the coin being used in the metapool + @param _A Amplification co-efficient - a higher value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _implementation_idx Index of the implementation to use. All possible + implementations for a BASE_POOL can be publicly accessed + via `metapool_implementations(BASE_POOL)` + @return Address of the deployed pool + """ + assert _fee <= 100000000, "Invalid fee" + + implementation: address = self.base_pool_data[_base_pool].implementations[_implementation_idx] + assert implementation != empty(address), "Invalid implementation index" + + # things break if a token has >18 decimals + decimals: uint256 = ERC20(_coin).decimals() + assert decimals < 19, "Max 18 decimals for coins" + + pool: address = create_minimal_proxy_to(implementation) + CurvePool(pool).initialize(_name, _symbol, _coin, 10 ** (36 - decimals), _A, _fee) + ERC20(_coin).approve(pool, max_value(uint256)) + + # add pool to pool_list + length: uint256 = self.pool_count + self.pool_list[length] = pool + self.pool_count = length + 1 + + base_lp_token: address = self.base_pool_data[_base_pool].lp_token + + self.pool_data[pool].decimals = [decimals, 0, 0, 0] + self.pool_data[pool].n_coins = 2 + self.pool_data[pool].base_pool = _base_pool + self.pool_data[pool].coins[0] = _coin + self.pool_data[pool].coins[1] = self.base_pool_data[_base_pool].lp_token + self.pool_data[pool].implementation = implementation + + is_finished: bool = False + for i in range(MAX_COINS): + swappable_coin: address = self.base_pool_data[_base_pool].coins[i] + if swappable_coin == empty(address): + is_finished = True + swappable_coin = base_lp_token + + key: uint256 = (convert(_coin, uint256) ^ convert(swappable_coin, uint256)) + length = self.market_counts[key] + self.markets[key][length] = pool + self.market_counts[key] = length + 1 + if is_finished: + break + + log MetaPoolDeployed(_coin, _base_pool, _A, _fee, msg.sender) + return pool + + +@external +def deploy_gauge(_pool: address) -> address: + """ + @notice Deploy a liquidity gauge for a factory pool + @param _pool Factory pool address to deploy a gauge for + @return Address of the deployed gauge + """ + assert self.pool_data[_pool].coins[0] != empty(address), "Unknown pool" + assert self.pool_data[_pool].liquidity_gauge == empty(address), "Gauge already deployed" + implementation: address = self.gauge_implementation + assert implementation != empty(address), "Gauge implementation not set" + + gauge: address = create_minimal_proxy_to(implementation) + LiquidityGauge(gauge).initialize(_pool) + self.pool_data[_pool].liquidity_gauge = gauge + + log LiquidityGaugeDeployed(_pool, gauge) + return gauge + + +# <--- Admin / Guarded Functionality ---> + +@external +def add_base_pool( + _base_pool: address, + _fee_receiver: address, + _asset_type: uint256, + _implementations: address[10], +): + """ + @notice Add a base pool to the registry, which may be used in factory metapools + @dev Only callable by admin + @param _base_pool Pool address to add + @param _fee_receiver Admin fee receiver address for metapools using this base pool + @param _asset_type Asset type for pool, as an integer 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _implementations List of implementation addresses that can be used with this base pool + """ + assert msg.sender == self.admin # dev: admin-only function + assert self.base_pool_data[_base_pool].coins[0] == empty(address) # dev: pool exists + + registry: address = AddressProvider(ADDRESS_PROVIDER).get_registry() + n_coins: uint256 = Registry(registry).get_n_coins(_base_pool) + + # add pool to pool_list + length: uint256 = self.base_pool_count + self.base_pool_list[length] = _base_pool + self.base_pool_count = length + 1 + self.base_pool_data[_base_pool].lp_token = Registry(registry).get_lp_token(_base_pool) + self.base_pool_data[_base_pool].n_coins = n_coins + self.base_pool_data[_base_pool].fee_receiver = _fee_receiver + if _asset_type != 0: + self.base_pool_data[_base_pool].asset_type = _asset_type + + for i in range(10): + implementation: address = _implementations[i] + if implementation == empty(address): + break + self.base_pool_data[_base_pool].implementations[i] = implementation + + decimals: uint256 = 0 + coins: address[MAX_COINS] = Registry(registry).get_coins(_base_pool) + for i in range(MAX_COINS): + if i == n_coins: + break + coin: address = coins[i] + self.base_pool_data[_base_pool].coins[i] = coin + self.base_pool_assets[coin] = True + decimals += (ERC20(coin).decimals() << i*8) + self.base_pool_data[_base_pool].decimals = decimals + + log BasePoolAdded(_base_pool) + + +@external +def set_metapool_implementations( + _base_pool: address, + _implementations: address[10], +): + """ + @notice Set implementation contracts for a metapool + @dev Only callable by admin + @param _base_pool Pool address to add + @param _implementations Implementation address to use when deploying metapools + """ + assert msg.sender == self.admin # dev: admin-only function + assert self.base_pool_data[_base_pool].coins[0] != empty(address) # dev: base pool does not exist + + for i in range(10): + new_imp: address = _implementations[i] + current_imp: address = self.base_pool_data[_base_pool].implementations[i] + if new_imp == current_imp: + if new_imp == empty(address): + break + else: + self.base_pool_data[_base_pool].implementations[i] = new_imp + + +@external +def set_plain_implementations( + _n_coins: uint256, + _implementations: address[10], +): + assert msg.sender == self.admin # dev: admin-only function + + for i in range(10): + new_imp: address = _implementations[i] + current_imp: address = self.plain_implementations[_n_coins][i] + if new_imp == current_imp: + if new_imp == empty(address): + break + else: + self.plain_implementations[_n_coins][i] = new_imp + + +@external +def set_gauge_implementation(_gauge_implementation: address): + assert msg.sender == self.admin # dev: admin-only function + + self.gauge_implementation = _gauge_implementation + + +@external +def batch_set_pool_asset_type(_pools: address[32], _asset_types: uint256[32]): + """ + @notice Batch set the asset type for factory pools + @dev Used to modify asset types that were set incorrectly at deployment + """ + assert msg.sender in [self.manager, self.admin] # dev: admin-only function + + for i in range(32): + if _pools[i] == empty(address): + break + self.pool_data[_pools[i]].asset_type = _asset_types[i] + + +@external +def commit_transfer_ownership(_addr: address): + """ + @notice Transfer ownership of this contract to `addr` + @param _addr Address of the new owner + """ + assert msg.sender == self.admin # dev: admin only + + self.future_admin = _addr + + +@external +def accept_transfer_ownership(): + """ + @notice Accept a pending ownership transfer + @dev Only callable by the new owner + """ + _admin: address = self.future_admin + assert msg.sender == _admin # dev: future admin only + + self.admin = _admin + self.future_admin = empty(address) + + +@external +def set_manager(_manager: address): + """ + @notice Set the manager + @dev Callable by the admin or existing manager + @param _manager Manager address + """ + assert msg.sender in [self.manager, self.admin] # dev: admin-only function + + self.manager = _manager + + +@external +def set_fee_receiver(_base_pool: address, _fee_receiver: address): + """ + @notice Set fee receiver for base and plain pools + @param _base_pool Address of base pool to set fee receiver for. + For plain pools, leave as `empty(address)`. + @param _fee_receiver Address that fees are sent to + """ + assert msg.sender == self.admin # dev: admin only + if _base_pool == empty(address): + self.fee_receiver = _fee_receiver + else: + self.base_pool_data[_base_pool].fee_receiver = _fee_receiver + + +@external +def convert_metapool_fees() -> bool: + """ + @notice Convert the fees of a metapool and transfer to + the metapool's fee receiver + @dev All fees are converted to LP token of base pool + """ + base_pool: address = self.pool_data[msg.sender].base_pool + assert base_pool != empty(address) # dev: sender must be metapool + coin: address = self.pool_data[msg.sender].coins[0] + + amount: uint256 = ERC20(coin).balanceOf(self) + receiver: address = self.base_pool_data[base_pool].fee_receiver + + CurvePool(msg.sender).exchange(0, 1, amount, 0, receiver) + return True diff --git a/contracts/CurveStableSwapNGFactoryHandler.vy b/contracts/CurveStableSwapNGFactoryHandler.vy new file mode 100644 index 00000000..9e7184e9 --- /dev/null +++ b/contracts/CurveStableSwapNGFactoryHandler.vy @@ -0,0 +1,542 @@ +# @version 0.3.9 +""" +@title CurveStableswapFactoryHandler +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2023 - all rights reserved +@notice StableFactory handler for the Metaregistry +""" + +# ---- interfaces ---- # +interface BaseRegistry: + def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address: view + def get_admin_balances(_pool: address) -> uint256[MAX_COINS]: view + def get_A(_pool: address) -> uint256: view + def get_balances(_pool: address) -> uint256[MAX_COINS]: view + def get_base_pool(_pool: address) -> address: view + def get_coins(_pool: address) -> address[MAX_COINS]: view + def get_coin_indices(_pool: address, _from: address, _to: address) -> (int128, int128): view + def get_decimals(_pool: address) -> uint256[MAX_COINS]: view + def get_fees(_pool: address) -> uint256[2]: view + def get_gauge(_pool: address) -> address: view + def get_lp_token(_pool: address) -> address: view + def get_meta_n_coins(_pool: address) -> (uint256, uint256): view + def get_n_coins(_pool: address) -> uint256: view + def get_pool_asset_type(_pool: address) -> uint256: view + def get_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def get_underlying_coins(_pool: address) -> address[MAX_COINS]: view + def get_underlying_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def is_meta(_pool: address) -> bool: view + def pool_count() -> uint256: view + def pool_list(pool_id: uint256) -> address: view + + +interface BasePoolRegistry: + def get_base_pool_for_lp_token(_lp_token: address) -> address: view + def get_n_coins(_pool: address) -> uint256: view + def get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view + def get_lp_token(_pool: address) -> address: view + def is_legacy(_pool: address) -> bool: view + def base_pool_list(i: uint256) -> address: view + + +interface CurveLegacyPool: + def balances(i: int128) -> uint256: view + + +interface CurvePool: + def admin_balances(i: uint256) -> uint256: view + def balances(i: uint256) -> uint256: view + def get_virtual_price() -> uint256: view + + +interface ERC20: + def balanceOf(_addr: address) -> uint256: view + def decimals() -> uint256: view + def name() -> String[64]: view + def totalSupply() -> uint256: view + + +interface GaugeController: + def gauge_types(gauge: address) -> int128: view + def gauges(i: uint256) -> address: view + + +interface Gauge: + def is_killed() -> bool: view + + +interface MetaRegistry: + def registry_length() -> uint256: view + + +# ---- constants ---- # +GAUGE_CONTROLLER: constant(address) = 0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB +MAX_COINS: constant(uint256) = 4 +MAX_METAREGISTRY_COINS: constant(uint256) = 8 + + +# ---- storage variables ---- # +base_registry: public(BaseRegistry) +base_pool_registry: public(BasePoolRegistry) + + +# ---- constructor ---- # +@external +def __init__(_registry_address: address, _base_pool_registry: address): + self.base_registry = BaseRegistry(_registry_address) + self.base_pool_registry = BasePoolRegistry(_base_pool_registry) + + +# ---- internal methods ---- # +@internal +@view +def _is_meta(_pool: address) -> bool: + return self.base_registry.is_meta(_pool) + + +@internal +@view +def _get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: + _coins: address[MAX_COINS] = self.base_registry.get_coins(_pool) + _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) + for i in range(MAX_COINS): + _padded_coins[i] = _coins[i] + return _padded_coins + + +@internal +@view +def _get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: + _coins: address[MAX_COINS] = self.base_registry.get_underlying_coins(_pool) + _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) + for i in range(MAX_COINS): + _padded_coins[i] = _coins[i] + return _padded_coins + + +@internal +@view +def _get_n_coins(_pool: address) -> uint256: + if self._is_meta(_pool): + return 2 + return self.base_registry.get_n_coins(_pool) + + +@internal +@view +def _get_base_pool(_pool: address) -> address: + _coins: address[MAX_METAREGISTRY_COINS] = self._get_coins(_pool) + _base_pool: address = empty(address) + for coin in _coins: + _base_pool = self.base_pool_registry.get_base_pool_for_lp_token(coin) + if _base_pool != empty(address): + return _base_pool + return empty(address) + + +@view +@internal +def _get_meta_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + base_coin_idx: uint256 = self._get_n_coins(_pool) - 1 + base_pool: address = self._get_base_pool(_pool) + base_total_supply: uint256 = ERC20(self.base_pool_registry.get_lp_token(base_pool)).totalSupply() + + ul_balance: uint256 = 0 + underlying_pct: uint256 = 0 + if base_total_supply > 0: + underlying_pct = CurvePool(_pool).balances(base_coin_idx) * 10**36 / base_total_supply + + underlying_balances: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) + ul_coins: address[MAX_METAREGISTRY_COINS] = self._get_underlying_coins(_pool) + for i in range(MAX_METAREGISTRY_COINS): + + if ul_coins[i] == empty(address): + break + + if i < base_coin_idx: + ul_balance = CurvePool(_pool).balances(i) + + else: + + if self.base_pool_registry.is_legacy(base_pool): + ul_balance = CurveLegacyPool(base_pool).balances(convert(i - base_coin_idx, int128)) + else: + ul_balance = CurvePool(base_pool).balances(i - base_coin_idx) + ul_balance = ul_balance * underlying_pct / 10**36 + underlying_balances[i] = ul_balance + + return underlying_balances + + +@internal +@view +def _pad_uint_array(_array: uint256[MAX_COINS]) -> uint256[MAX_METAREGISTRY_COINS]: + _padded_array: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) + for i in range(MAX_COINS): + _padded_array[i] = _array[i] + return _padded_array + + +@internal +@view +def _get_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + return self._pad_uint_array(self.base_registry.get_balances(_pool)) + + +@internal +@view +def _get_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + return self._pad_uint_array(self.base_registry.get_decimals(_pool)) + + +@internal +@view +def _get_gauge_type(_gauge: address) -> int128: + + success: bool = False + response: Bytes[32] = b"" + success, response = raw_call( + GAUGE_CONTROLLER, + concat( + method_id("gauge_type(address)"), + convert(_gauge, bytes32), + ), + max_outsize=32, + revert_on_failure=False, + is_static_call=True + ) + + if success and not Gauge(_gauge).is_killed(): + return convert(response, int128) + + return 0 + + +# ---- view methods (API) of the contract ---- # +@external +@view +def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address: + return self.base_registry.find_pool_for_coins(_from, _to, i) + + +@external +@view +def get_admin_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + """ + @notice Get the balances of the admin of the pool + @dev does not use base registry admin_balances because that has errors + in the getter for n_coins (some pools show zero, so admin balances is zero) + @param _pool address of the pool + @return balances of the admin of the pool + """ + n_coins: uint256 = self._get_n_coins(_pool) + admin_balances: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) + for i in range(MAX_METAREGISTRY_COINS): + if i == n_coins: + break + admin_balances[i] = CurvePool(_pool).admin_balances(i) + return admin_balances + + +@external +@view +def get_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + """ + @notice Get the balances of the pool + @param _pool address of the pool + @return balances of the pool + """ + return self._get_balances(_pool) + + +@external +@view +def get_base_pool(_pool: address) -> address: + """ + @notice Get the base pool of the pool + @param _pool address of the pool + @return base pool of the pool + """ + return self._get_base_pool(_pool) + + +@view +@external +def get_coin_indices(_pool: address, _from: address, _to: address) -> (int128, int128, bool): + """ + @notice Get the indices of the coins in the pool + @param _pool address of the pool + @param _from address of the coin + @param _to address of the coin + @return coin indices and whether the coin swap involves an underlying market or not + """ + coin1: int128 = 0 + coin2: int128 = 0 + is_underlying: bool = False + + (coin1, coin2) = self.base_registry.get_coin_indices(_pool, _from, _to) + + # due to a bug in original factory contract, `is_underlying`` is always True + # to fix this, we first check if it is a metapool, and if not then we return + # False. If so, then we check if basepool lp token is one of the two coins, + # in which case `is_underlying` would be False + if self._is_meta(_pool): + base_pool_lp_token: address = self.base_registry.get_coins(_pool)[1] + if base_pool_lp_token not in [_from, _to]: + is_underlying = True + + return (coin1, coin2, is_underlying) + + +@external +@view +def get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: + """ + @notice Get the coins of the pool + @param _pool address of the pool + @return coins of the pool + """ + return self._get_coins(_pool) + + +@external +@view +def get_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + """ + @notice Get the decimals of coins in the pool + @param _pool address of the pool + @return decimals of coins in the pool + """ + return self._get_decimals(_pool) + + +@external +@view +def get_fees(_pool: address) -> uint256[10]: + """ + @notice Get the fees of the pool + @param _pool address of the pool + @return fees of the pool + """ + fees: uint256[10] = empty(uint256[10]) + pool_fees: uint256[2] = self.base_registry.get_fees(_pool) + for i in range(2): + fees[i] = pool_fees[i] + return fees + + +@external +@view +def get_virtual_price_from_lp_token(_pool: address) -> uint256: + """ + @notice Get the virtual price of the pool + @param _pool address of the pool + @return virtual price of the pool + """ + return CurvePool(_pool).get_virtual_price() + + +@external +@view +def get_gauges(_pool: address) -> (address[10], int128[10]): + """ + @notice Get the gauges and gauge types of the pool + @param _pool address of the pool + @return gauges of the pool + """ + gauges: address[10] = empty(address[10]) + types: int128[10] = empty(int128[10]) + gauges[0] = self.base_registry.get_gauge(_pool) + types[0] = self._get_gauge_type(gauges[0]) + return (gauges, types) + + +@external +@view +def get_lp_token(_pool: address) -> address: + """ + @notice Get the lp token of the pool + @dev for stableswap factory pools, the pool is the lp token itself + @param _pool address of the pool + @return lp token of the pool + """ + return _pool + + +@external +@view +def get_n_coins(_pool: address) -> uint256: + """ + @notice Get the number of coins in the pool + @param _pool address of the pool + @return number of coins in the pool + """ + return self._get_n_coins(_pool) + + +@external +@view +def get_n_underlying_coins(_pool: address) -> uint256: + """ + @notice Get the number of underlying coins in the pool + @param _pool address of the pool + @return number of underlying coins in the pool + """ + # need to check if any of the token is a base pool LP token + # since a metapool can be lptoken:lptoken, and it would count + # underlying coins as 1 + base_pool_n_coins instead of 2 x base_pool_n_coins + coins: address[MAX_METAREGISTRY_COINS] = self._get_coins(_pool) + base_pool: address = empty(address) + num_coins: uint256 = 0 + for i in range(MAX_METAREGISTRY_COINS): + + if coins[i] == empty(address): + break + + base_pool = self.base_pool_registry.get_base_pool_for_lp_token(coins[i]) + if base_pool == empty(address) and coins[i] != empty(address): + num_coins += 1 + else: + num_coins += self.base_pool_registry.get_n_coins(base_pool) + + return num_coins + + +@external +@view +def get_pool_asset_type(_pool: address) -> uint256: + """ + @notice Get the asset type of the coins in the pool + @dev 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _pool address of the pool + @return pool asset type of the pool + """ + return self.base_registry.get_pool_asset_type(_pool) + + +@external +@view +def get_pool_from_lp_token(_lp_token: address) -> address: + """ + @notice Get the pool of the lp token + @dev This is more or less like a pass through method. Can be ignored but + We leave it in for consistency across registry handlers. + @param _lp_token address of the lp token (which is also the pool) + @return pool of the lp token + """ + if self._get_n_coins(_lp_token) > 0: + return _lp_token + return empty(address) + + +@external +@view +def get_pool_name(_pool: address) -> String[64]: + """ + @notice Get the name of the pool + @dev stable factory pools are ERC20 tokenized + @return name of the pool + """ + if self._get_n_coins(_pool) == 0: + # _pool is not in base registry, so we ignore: + return "" + return ERC20(_pool).name() + + +@external +@view +def get_pool_params(_pool: address) -> uint256[20]: + """ + @notice Get the parameters of the pool + @param _pool address of the pool + @return parameters of the pool + """ + stableswap_pool_params: uint256[20] = empty(uint256[20]) + stableswap_pool_params[0] = self.base_registry.get_A(_pool) + return stableswap_pool_params + + +@external +@view +def get_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + """ + @notice Get the underlying balances of the pool + @param _pool address of the pool + @return underlying balances of the pool + """ + if not self._is_meta(_pool): + return self._get_balances(_pool) + return self._get_meta_underlying_balances(_pool) + + +@external +@view +def get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: + """ + @notice Get the underlying coins of the pool + @param _pool address of the pool + @return underlying coins of the pool + """ + if not self._is_meta(_pool): + return self._get_coins(_pool) + return self._get_underlying_coins(_pool) + + +@external +@view +def get_underlying_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: + """ + @notice Get the underlying decimals of the pool + @dev If it is a metapool, method uses the base registry. Else it uses a + custom getter. This is because the base registry cannot unpack decimals + (stored as a bitmap) if there is no metapool. So it returns the decimals of + only the first coin. + @param _pool Address of the pool + @return underlying decimals of the pool + """ + if not self._is_meta(_pool): + return self._get_decimals(_pool) + return self.base_registry.get_underlying_decimals(_pool) + + +@external +@view +def is_meta(_pool: address) -> bool: + """ + @notice Check if the pool is a metapool + @param _pool address of the pool + @return True if the pool is a metapool + """ + return self._is_meta(_pool) + + +@external +@view +def is_registered(_pool: address) -> bool: + """ + @notice Check if a pool belongs to the registry using get_n_coins + @param _pool The address of the pool + @return A bool corresponding to whether the pool belongs or not + """ + return self._get_n_coins(_pool) > 0 + + +@external +@view +def pool_count() -> uint256: + """ + @notice Get the number of pools in the registry + @return number of pools in the registry + """ + return self.base_registry.pool_count() + + +@external +@view +def pool_list(_index: uint256) -> address: + """ + @notice Get the address of the pool at the given index + @param _index The index of the pool + @return The address of the pool + """ + return self.base_registry.pool_list(_index) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e6799ecd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +# linting: +black +flake8 +isort +mamushi +pip-tools +pre-commit + +# testing: +eip712 +eth_account +ipython +hypothesis +pytest +pytest-xdist +pytest-forked +pytest-repeat +pdbpp +hypothesis>=6.68.1 + +# vyper and dev framework: +git+https://github.com/vyperlang/titanoboa.git@bd4af917754e7a66a84dee6134c8a42112224471 +vyper>=0.3.9 From 173d015dd1cc2cd28aad9c1b5f6efb698bb9ffeb Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:49:06 +0200 Subject: [PATCH 003/337] add other contracts --- contracts/{ => main}/CurveStableSwap2NG.vy | 17 +- .../{ => main}/CurveStableSwapFactoryNG.vy | 18 +- .../CurveStableSwapFactoryNGHandler.vy} | 2 +- contracts/main/LiquidityGauge.vy | 848 ++++++++++++++++++ contracts/mock/CallbackSwap.vy | 113 +++ contracts/mock/CallbackTestZap.vy | 66 ++ contracts/mock/ERC20Mock.vy | 62 ++ contracts/mock/WETH.vy | 82 ++ tests/__init__.py | 0 tests/conftest.py | 0 tests/fixtures/accounts.py | 72 ++ tests/fixtures/factory.py | 13 + tests/fixtures/pool.py | 0 tests/fixtures/tokens.py | 36 + tests/utils/tokens.py | 17 + 15 files changed, 1315 insertions(+), 31 deletions(-) rename contracts/{ => main}/CurveStableSwap2NG.vy (99%) rename contracts/{ => main}/CurveStableSwapFactoryNG.vy (98%) rename contracts/{CurveStableSwapNGFactoryHandler.vy => main/CurveStableSwapFactoryNGHandler.vy} (99%) create mode 100644 contracts/main/LiquidityGauge.vy create mode 100644 contracts/mock/CallbackSwap.vy create mode 100644 contracts/mock/CallbackTestZap.vy create mode 100644 contracts/mock/ERC20Mock.vy create mode 100644 contracts/mock/WETH.vy create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/fixtures/accounts.py create mode 100644 tests/fixtures/factory.py create mode 100644 tests/fixtures/pool.py create mode 100644 tests/fixtures/tokens.py create mode 100644 tests/utils/tokens.py diff --git a/contracts/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy similarity index 99% rename from contracts/CurveStableSwap2NG.vy rename to contracts/main/CurveStableSwap2NG.vy index 490433c0..3e2cb291 100644 --- a/contracts/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -163,17 +163,7 @@ ma_last_time: public(uint256) @external -def __init__(_weth: address): - - WETH20 = _weth - - # we do this to prevent the implementation contract from being used as a pool - self.factory = 0x0000000000000000000000000000000000000001 - assert N_COINS == 2 - - -@external -def initialize( +def __init__( _name: String[32], _symbol: String[10], _coins: address[4], @@ -183,7 +173,7 @@ def initialize( _weth: address, _ma_exp_time: uint256, _method_ids: bytes4[4], - _oracles: address[4] + _oracles: address[4], ): """ @notice Initialize the pool contract @@ -208,8 +198,7 @@ def initialize( @param _oracles Array of rate oracle addresses. """ - # check if factory was already set to prevent initializing contract twice - assert self.factory == empty(address) + WETH20 = _weth for i in range(N_COINS): diff --git a/contracts/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy similarity index 98% rename from contracts/CurveStableSwapFactoryNG.vy rename to contracts/main/CurveStableSwapFactoryNG.vy index 391c4c24..7afa18a1 100644 --- a/contracts/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,6 +1,6 @@ # @version 0.3.9 """ -@title CurveStableswapFactory +@title CurveStableswapFactoryNG @author Curve.Fi @license Copyright (c) Curve.Fi, 2023 - all rights reserved @notice Permissionless pool deployer and registry @@ -118,7 +118,6 @@ OLD_FACTORY: constant(address) = 0x0959158b6040D32d04c301A72CBFD6b39E21c9AE admin: public(address) future_admin: public(address) -manager: public(address) pool_list: public(address[4294967296]) # master list of pools pool_count: public(uint256) # actual length of pool_list @@ -154,7 +153,6 @@ def __init__(_fee_receiver: address, _weth: address): WETH20 = _weth self.admin = msg.sender - self.manager = msg.sender self.fee_receiver = _fee_receiver @@ -835,7 +833,7 @@ def batch_set_pool_asset_type(_pools: address[32], _asset_types: uint256[32]): @notice Batch set the asset type for factory pools @dev Used to modify asset types that were set incorrectly at deployment """ - assert msg.sender in [self.manager, self.admin] # dev: admin-only function + assert msg.sender in [self.admin] # dev: admin-only function for i in range(32): if _pools[i] == empty(address): @@ -867,18 +865,6 @@ def accept_transfer_ownership(): self.future_admin = empty(address) -@external -def set_manager(_manager: address): - """ - @notice Set the manager - @dev Callable by the admin or existing manager - @param _manager Manager address - """ - assert msg.sender in [self.manager, self.admin] # dev: admin-only function - - self.manager = _manager - - @external def set_fee_receiver(_base_pool: address, _fee_receiver: address): """ diff --git a/contracts/CurveStableSwapNGFactoryHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy similarity index 99% rename from contracts/CurveStableSwapNGFactoryHandler.vy rename to contracts/main/CurveStableSwapFactoryNGHandler.vy index 9e7184e9..caf7d7b0 100644 --- a/contracts/CurveStableSwapNGFactoryHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,6 +1,6 @@ # @version 0.3.9 """ -@title CurveStableswapFactoryHandler +@title CurveStableswapFactoryNGHandler @author Curve.Fi @license Copyright (c) Curve.Fi, 2023 - all rights reserved @notice StableFactory handler for the Metaregistry diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy new file mode 100644 index 00000000..bf4347b6 --- /dev/null +++ b/contracts/main/LiquidityGauge.vy @@ -0,0 +1,848 @@ +# @version 0.3.9 + +""" +@title LiquidityGaugeV6 +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Implementation contract for use with Curve Factory +@dev Differs from v5.0.0 in that it uses create_from_blueprint to deploy Gauges +""" +from vyper.interfaces import ERC20 + +implements: ERC20 + + +interface CRV20: + def future_epoch_time_write() -> uint256: nonpayable + def rate() -> uint256: view + +interface Controller: + def checkpoint_gauge(addr: address): nonpayable + def gauge_relative_weight(addr: address, time: uint256) -> uint256: view + +interface ERC20Extended: + def symbol() -> String[32]: view + +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + +interface Factory: + def admin() -> address: view + +interface Minter: + def minted(user: address, gauge: address) -> uint256: view + +interface VotingEscrow: + def user_point_epoch(addr: address) -> uint256: view + def user_point_history__ts(addr: address, epoch: uint256) -> uint256: view + +interface VotingEscrowBoost: + def adjusted_balance_of(_account: address) -> uint256: view + + +event Deposit: + provider: indexed(address) + value: uint256 + +event Withdraw: + provider: indexed(address) + value: uint256 + +event UpdateLiquidityLimit: + user: indexed(address) + original_balance: uint256 + original_supply: uint256 + working_balance: uint256 + working_supply: uint256 + +event CommitOwnership: + admin: address + +event ApplyOwnership: + admin: address + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +struct Reward: + token: address + distributor: address + period_finish: uint256 + rate: uint256 + last_update: uint256 + integral: uint256 + + +MAX_REWARDS: constant(uint256) = 8 +TOKENLESS_PRODUCTION: constant(uint256) = 40 +WEEK: constant(uint256) = 604800 + +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +VERSION: constant(String[8]) = "v6.0.0" # <- updated from v5.0.0 (adds `create_from_blueprint` pattern) + +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +VERSION_HASH: constant(bytes32) = keccak256(VERSION) +NAME_HASH: immutable(bytes32) +CACHED_CHAIN_ID: immutable(uint256) +salt: public(immutable(bytes32)) +CACHED_DOMAIN_SEPARATOR: immutable(bytes32) + +CRV: constant(address) = 0xD533a949740bb3306d119CC777fa900bA034cd52 +GAUGE_CONTROLLER: constant(address) = 0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB +MINTER: constant(address) = 0xd061D61a4d941c39E5453435B6345Dc261C2fcE0 +VEBOOST_PROXY: constant(address) = 0x8E0c00ed546602fD9927DF742bbAbF726D5B0d16 +VOTING_ESCROW: constant(address) = 0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2 + + +# ERC20 +balanceOf: public(HashMap[address, uint256]) +totalSupply: public(uint256) +allowance: public(HashMap[address, HashMap[address, uint256]]) + +name: public(String[64]) +symbol: public(String[40]) + +# ERC2612 +nonces: public(HashMap[address, uint256]) + +# Gauge +factory: public(address) +lp_token: public(address) + +is_killed: public(bool) + +# [future_epoch_time uint40][inflation_rate uint216] +inflation_params: uint256 + +# For tracking external rewards +reward_count: public(uint256) +reward_data: public(HashMap[address, Reward]) + +# claimant -> default reward receiver +rewards_receiver: public(HashMap[address, address]) + +# reward token -> claiming address -> integral +reward_integral_for: public(HashMap[address, HashMap[address, uint256]]) + +# user -> [uint128 claimable amount][uint128 claimed amount] +claim_data: HashMap[address, HashMap[address, uint256]] + +working_balances: public(HashMap[address, uint256]) +working_supply: public(uint256) + +# 1e18 * ∫(rate(t) / totalSupply(t) dt) from (last_action) till checkpoint +integrate_inv_supply_of: public(HashMap[address, uint256]) +integrate_checkpoint_of: public(HashMap[address, uint256]) + +# ∫(balance * rate(t) / totalSupply(t) dt) from 0 till checkpoint +# Units: rate * t = already number of coins per address to issue +integrate_fraction: public(HashMap[address, uint256]) + +# The goal is to be able to calculate ∫(rate * balance / totalSupply dt) from 0 till checkpoint +# All values are kept in units of being multiplied by 1e18 +period: public(int128) + +# array of reward tokens +reward_tokens: public(address[MAX_REWARDS]) + +period_timestamp: public(uint256[100000000000000000000000000000]) +# 1e18 * ∫(rate(t) / totalSupply(t) dt) from 0 till checkpoint +integrate_inv_supply: public(uint256[100000000000000000000000000000]) # bump epoch when rate() changes + + +@external +def __init__(_lp_token: address): + """ + @notice Contract constructor + @param _lp_token Liquidity Pool contract address + """ + assert self.lp_token == empty(address) + + self.lp_token = _lp_token + self.factory = msg.sender + + symbol: String[32] = ERC20Extended(_lp_token).symbol() + name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") + + self.name = name + self.symbol = concat(symbol, "-gauge") + + self.period_timestamp[0] = block.timestamp + self.inflation_params = ( + (CRV20(CRV).future_epoch_time_write() << 216) + + CRV20(CRV).rate() + ) + + NAME_HASH = keccak256(name) + salt = block.prevhash + CACHED_CHAIN_ID = chain.id + CACHED_DOMAIN_SEPARATOR = keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + + +# Internal Functions + +@view +@internal +def _domain_separator() -> bytes32: + if chain.id != CACHED_CHAIN_ID: + return keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + return CACHED_DOMAIN_SEPARATOR + + +@internal +def _checkpoint(addr: address): + """ + @notice Checkpoint for a user + @dev Updates the CRV emissions a user is entitled to receive + @param addr User address + """ + _period: int128 = self.period + _period_time: uint256 = self.period_timestamp[_period] + _integrate_inv_supply: uint256 = self.integrate_inv_supply[_period] + + inflation_params: uint256 = self.inflation_params + rate: uint256 = inflation_params % 2 ** 216 + prev_future_epoch: uint256 = inflation_params >> 216 + new_rate: uint256 = rate + + if prev_future_epoch >= _period_time: + new_rate = CRV20(CRV).rate() + self.inflation_params = (CRV20(CRV).future_epoch_time_write() << 216) + new_rate + + if self.is_killed: + # Stop distributing inflation as soon as killed + rate = 0 + new_rate = 0 # prevent distribution when crossing epochs + + # Update integral of 1/supply + if block.timestamp > _period_time: + _working_supply: uint256 = self.working_supply + Controller(GAUGE_CONTROLLER).checkpoint_gauge(self) + prev_week_time: uint256 = _period_time + week_time: uint256 = min((_period_time + WEEK) / WEEK * WEEK, block.timestamp) + + for i in range(500): + dt: uint256 = week_time - prev_week_time + w: uint256 = Controller(GAUGE_CONTROLLER).gauge_relative_weight(self, prev_week_time / WEEK * WEEK) + + if _working_supply > 0: + if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time: + # If we went across one or multiple epochs, apply the rate + # of the first epoch until it ends, and then the rate of + # the last epoch. + # If more than one epoch is crossed - the gauge gets less, + # but that'd meen it wasn't called for more than 1 year + _integrate_inv_supply += rate * w * (prev_future_epoch - prev_week_time) / _working_supply + rate = new_rate + _integrate_inv_supply += rate * w * (week_time - prev_future_epoch) / _working_supply + else: + _integrate_inv_supply += rate * w * dt / _working_supply + # On precisions of the calculation + # rate ~= 10e18 + # last_weight > 0.01 * 1e18 = 1e16 (if pool weight is 1%) + # _working_supply ~= TVL * 1e18 ~= 1e26 ($100M for example) + # The largest loss is at dt = 1 + # Loss is 1e-9 - acceptable + + if week_time == block.timestamp: + break + prev_week_time = week_time + week_time = min(week_time + WEEK, block.timestamp) + + _period += 1 + self.period = _period + self.period_timestamp[_period] = block.timestamp + self.integrate_inv_supply[_period] = _integrate_inv_supply + + # Update user-specific integrals + _working_balance: uint256 = self.working_balances[addr] + self.integrate_fraction[addr] += _working_balance * (_integrate_inv_supply - self.integrate_inv_supply_of[addr]) / 10 ** 18 + self.integrate_inv_supply_of[addr] = _integrate_inv_supply + self.integrate_checkpoint_of[addr] = block.timestamp + + +@internal +def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _receiver: address): + """ + @notice Claim pending rewards and checkpoint rewards for a user + """ + user_balance: uint256 = 0 + receiver: address = _receiver + if _user != empty(address): + user_balance = self.balanceOf[_user] + if _claim and _receiver == empty(address): + # if receiver is not explicitly declared, check if a default receiver is set + receiver = self.rewards_receiver[_user] + if receiver == empty(address): + # if no default receiver is set, direct claims to the user + receiver = _user + + reward_count: uint256 = self.reward_count + for i in range(MAX_REWARDS): + if i == reward_count: + break + token: address = self.reward_tokens[i] + + integral: uint256 = self.reward_data[token].integral + last_update: uint256 = min(block.timestamp, self.reward_data[token].period_finish) + duration: uint256 = last_update - self.reward_data[token].last_update + if duration != 0: + self.reward_data[token].last_update = last_update + if _total_supply != 0: + integral += duration * self.reward_data[token].rate * 10**18 / _total_supply + self.reward_data[token].integral = integral + + if _user != empty(address): + integral_for: uint256 = self.reward_integral_for[token][_user] + new_claimable: uint256 = 0 + + if integral_for < integral: + self.reward_integral_for[token][_user] = integral + new_claimable = user_balance * (integral - integral_for) / 10**18 + + claim_data: uint256 = self.claim_data[_user][token] + total_claimable: uint256 = (claim_data >> 128) + new_claimable + if total_claimable > 0: + total_claimed: uint256 = claim_data % 2**128 + if _claim: + response: Bytes[32] = raw_call( + token, + _abi_encode( + receiver, + total_claimable, + method_id=method_id("transfer(address,uint256)") + ), + max_outsize=32, + ) + if len(response) != 0: + assert convert(response, bool) + self.claim_data[_user][token] = total_claimed + total_claimable + elif new_claimable > 0: + self.claim_data[_user][token] = total_claimed + (total_claimable << 128) + + +@internal +def _update_liquidity_limit(addr: address, l: uint256, L: uint256): + """ + @notice Calculate limits which depend on the amount of CRV token per-user. + Effectively it calculates working balances to apply amplification + of CRV production by CRV + @param addr User address + @param l User's amount of liquidity (LP tokens) + @param L Total amount of liquidity (LP tokens) + """ + # To be called after totalSupply is updated + voting_balance: uint256 = VotingEscrowBoost(VEBOOST_PROXY).adjusted_balance_of(addr) + voting_total: uint256 = ERC20(VOTING_ESCROW).totalSupply() + + lim: uint256 = l * TOKENLESS_PRODUCTION / 100 + if voting_total > 0: + lim += L * voting_balance / voting_total * (100 - TOKENLESS_PRODUCTION) / 100 + + lim = min(l, lim) + old_bal: uint256 = self.working_balances[addr] + self.working_balances[addr] = lim + _working_supply: uint256 = self.working_supply + lim - old_bal + self.working_supply = _working_supply + + log UpdateLiquidityLimit(addr, l, L, lim, _working_supply) + + +@internal +def _transfer(_from: address, _to: address, _value: uint256): + """ + @notice Transfer tokens as well as checkpoint users + """ + self._checkpoint(_from) + self._checkpoint(_to) + + if _value != 0: + total_supply: uint256 = self.totalSupply + is_rewards: bool = self.reward_count != 0 + if is_rewards: + self._checkpoint_rewards(_from, total_supply, False, empty(address)) + new_balance: uint256 = self.balanceOf[_from] - _value + self.balanceOf[_from] = new_balance + self._update_liquidity_limit(_from, new_balance, total_supply) + + if is_rewards: + self._checkpoint_rewards(_to, total_supply, False, empty(address)) + new_balance = self.balanceOf[_to] + _value + self.balanceOf[_to] = new_balance + self._update_liquidity_limit(_to, new_balance, total_supply) + + log Transfer(_from, _to, _value) + + +# External User Facing Functions + + +@external +@nonreentrant('lock') +def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = False): + """ + @notice Deposit `_value` LP tokens + @dev Depositting also claims pending reward tokens + @param _value Number of tokens to deposit + @param _addr Address to deposit for + """ + + self._checkpoint(_addr) + + if _value != 0: + is_rewards: bool = self.reward_count != 0 + total_supply: uint256 = self.totalSupply + if is_rewards: + self._checkpoint_rewards(_addr, total_supply, _claim_rewards, empty(address)) + + total_supply += _value + new_balance: uint256 = self.balanceOf[_addr] + _value + self.balanceOf[_addr] = new_balance + self.totalSupply = total_supply + + self._update_liquidity_limit(_addr, new_balance, total_supply) + + ERC20(self.lp_token).transferFrom(msg.sender, self, _value) + + log Deposit(_addr, _value) + log Transfer(empty(address), _addr, _value) + + +@external +@nonreentrant('lock') +def withdraw(_value: uint256, _claim_rewards: bool = False): + """ + @notice Withdraw `_value` LP tokens + @dev Withdrawing also claims pending reward tokens + @param _value Number of tokens to withdraw + """ + self._checkpoint(msg.sender) + + if _value != 0: + is_rewards: bool = self.reward_count != 0 + total_supply: uint256 = self.totalSupply + if is_rewards: + self._checkpoint_rewards(msg.sender, total_supply, _claim_rewards, empty(address)) + + total_supply -= _value + new_balance: uint256 = self.balanceOf[msg.sender] - _value + self.balanceOf[msg.sender] = new_balance + self.totalSupply = total_supply + + self._update_liquidity_limit(msg.sender, new_balance, total_supply) + + ERC20(self.lp_token).transfer(msg.sender, _value) + + log Withdraw(msg.sender, _value) + log Transfer(msg.sender, empty(address), _value) + + +@external +@nonreentrant('lock') +def claim_rewards(_addr: address = msg.sender, _receiver: address = empty(address)): + """ + @notice Claim available reward tokens for `_addr` + @param _addr Address to claim for + @param _receiver Address to transfer rewards to - if set to + empty(address), uses the default reward receiver + for the caller + """ + if _receiver != empty(address): + assert _addr == msg.sender # dev: cannot redirect when claiming for another user + self._checkpoint_rewards(_addr, self.totalSupply, True, _receiver) + + +@external +@nonreentrant('lock') +def transferFrom(_from: address, _to :address, _value: uint256) -> bool: + """ + @notice Transfer tokens from one address to another. + @dev Transferring claims pending reward tokens for the sender and receiver + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != max_value(uint256): + self.allowance[_from][msg.sender] = _allowance - _value + + self._transfer(_from, _to, _value) + + return True + + +@external +@nonreentrant('lock') +def transfer(_to: address, _value: uint256) -> bool: + """ + @notice Transfer token for a specified address + @dev Transferring claims pending reward tokens for the sender and receiver + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + self._transfer(msg.sender, _to, _value) + + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk + that someone may use both the old and new allowance by unfortunate + transaction ordering. This may be mitigated with the use of + {incraseAllowance} and {decreaseAllowance}. + https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + + return True + + +@external +def permit( + _owner: address, + _spender: address, + _value: uint256, + _deadline: uint256, + _v: uint8, + _r: bytes32, + _s: bytes32 +) -> bool: + """ + @notice Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 + @dev Supports smart contract wallets which implement ERC1271 + https://eips.ethereum.org/EIPS/eip-1271 + @param _owner The address which is a source of funds and has signed the Permit. + @param _spender The address which is allowed to spend the funds. + @param _value The amount of tokens to be spent. + @param _deadline The timestamp after which the Permit is no longer valid. + @param _v The bytes[64] of the valid secp256k1 signature of permit by owner + @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner + @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner + @return True, if transaction completes successfully + """ + assert _owner != empty(address) # dev: invalid owner + assert block.timestamp <= _deadline # dev: permit expired + + nonce: uint256 = self.nonces[_owner] + digest: bytes32 = keccak256( + concat( + b"\x19\x01", + self._domain_separator(), + keccak256( + _abi_encode( + EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline + ) + ), + ) + ) + assert ecrecover(digest, _v, _r, _s) == _owner # dev: invalid signature + + self.allowance[_owner][_spender] = _value + self.nonces[_owner] = nonce + 1 + + log Approval(_owner, _spender, _value) + return True + + +@external +def increaseAllowance(_spender: address, _added_value: uint256) -> bool: + """ + @notice Increase the allowance granted to `_spender` by the caller + @dev This is alternative to {approve} that can be used as a mitigation for + the potential race condition + @param _spender The address which will transfer the funds + @param _added_value The amount of to increase the allowance + @return bool success + """ + allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value + self.allowance[msg.sender][_spender] = allowance + + log Approval(msg.sender, _spender, allowance) + + return True + + +@external +def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool: + """ + @notice Decrease the allowance granted to `_spender` by the caller + @dev This is alternative to {approve} that can be used as a mitigation for + the potential race condition + @param _spender The address which will transfer the funds + @param _subtracted_value The amount of to decrease the allowance + @return bool success + """ + allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value + self.allowance[msg.sender][_spender] = allowance + + log Approval(msg.sender, _spender, allowance) + + return True + + +@external +def user_checkpoint(addr: address) -> bool: + """ + @notice Record a checkpoint for `addr` + @param addr User address + @return bool success + """ + assert msg.sender in [addr, MINTER] # dev: unauthorized + self._checkpoint(addr) + self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply) + return True + + +@external +def set_rewards_receiver(_receiver: address): + """ + @notice Set the default reward receiver for the caller. + @dev When set to empty(address), rewards are sent to the caller + @param _receiver Receiver address for any rewards claimed via `claim_rewards` + """ + self.rewards_receiver[msg.sender] = _receiver + + +@external +def kick(addr: address): + """ + @notice Kick `addr` for abusing their boost + @dev Only if either they had another voting event, or their voting escrow lock expired + @param addr Address to kick + """ + t_last: uint256 = self.integrate_checkpoint_of[addr] + t_ve: uint256 = VotingEscrow(VOTING_ESCROW).user_point_history__ts( + addr, VotingEscrow(VOTING_ESCROW).user_point_epoch(addr) + ) + _balance: uint256 = self.balanceOf[addr] + + assert ERC20(VOTING_ESCROW).balanceOf(addr) == 0 or t_ve > t_last # dev: kick not allowed + assert self.working_balances[addr] > _balance * TOKENLESS_PRODUCTION / 100 # dev: kick not needed + + self._checkpoint(addr) + self._update_liquidity_limit(addr, self.balanceOf[addr], self.totalSupply) + + +# Administrative Functions + + +@external +@nonreentrant("lock") +def deposit_reward_token(_reward_token: address, _amount: uint256): + """ + @notice Deposit a reward token for distribution + @param _reward_token The reward token being deposited + @param _amount The amount of `_reward_token` being deposited + """ + assert msg.sender == self.reward_data[_reward_token].distributor + + self._checkpoint_rewards(empty(address), self.totalSupply, False, empty(address)) + + response: Bytes[32] = raw_call( + _reward_token, + _abi_encode( + msg.sender, self, _amount, method_id=method_id("transferFrom(address,address,uint256)") + ), + max_outsize=32, + ) + if len(response) != 0: + assert convert(response, bool) + + period_finish: uint256 = self.reward_data[_reward_token].period_finish + if block.timestamp >= period_finish: + self.reward_data[_reward_token].rate = _amount / WEEK + else: + remaining: uint256 = period_finish - block.timestamp + leftover: uint256 = remaining * self.reward_data[_reward_token].rate + self.reward_data[_reward_token].rate = (_amount + leftover) / WEEK + + self.reward_data[_reward_token].last_update = block.timestamp + self.reward_data[_reward_token].period_finish = block.timestamp + WEEK + + +@external +def add_reward(_reward_token: address, _distributor: address): + """ + @notice Add additional rewards to be distributed to stakers + @param _reward_token The token to add as an additional reward + @param _distributor Address permitted to fund this contract with the reward token + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + + reward_count: uint256 = self.reward_count + assert reward_count < MAX_REWARDS + assert self.reward_data[_reward_token].distributor == empty(address) + + self.reward_data[_reward_token].distributor = _distributor + self.reward_tokens[reward_count] = _reward_token + self.reward_count = reward_count + 1 + + +@external +def set_reward_distributor(_reward_token: address, _distributor: address): + """ + @notice Reassign the reward distributor for a reward token + @param _reward_token The reward token to reassign distribution rights to + @param _distributor The address of the new distributor + """ + current_distributor: address = self.reward_data[_reward_token].distributor + + assert msg.sender == current_distributor or msg.sender == Factory(self.factory).admin() + assert current_distributor != empty(address) + assert _distributor != empty(address) + + self.reward_data[_reward_token].distributor = _distributor + + +@external +def set_killed(_is_killed: bool): + """ + @notice Set the killed status for this contract + @dev When killed, the gauge always yields a rate of 0 and so cannot mint CRV + @param _is_killed Killed status to set + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + + self.is_killed = _is_killed + + +# View Methods + + +@view +@external +def claimed_reward(_addr: address, _token: address) -> uint256: + """ + @notice Get the number of already-claimed reward tokens for a user + @param _addr Account to get reward amount for + @param _token Token to get reward amount for + @return uint256 Total amount of `_token` already claimed by `_addr` + """ + return self.claim_data[_addr][_token] % 2**128 + + +@view +@external +def claimable_reward(_user: address, _reward_token: address) -> uint256: + """ + @notice Get the number of claimable reward tokens for a user + @param _user Account to get reward amount for + @param _reward_token Token to get reward amount for + @return uint256 Claimable reward token amount + """ + integral: uint256 = self.reward_data[_reward_token].integral + total_supply: uint256 = self.totalSupply + if total_supply != 0: + last_update: uint256 = min(block.timestamp, self.reward_data[_reward_token].period_finish) + duration: uint256 = last_update - self.reward_data[_reward_token].last_update + integral += (duration * self.reward_data[_reward_token].rate * 10**18 / total_supply) + + integral_for: uint256 = self.reward_integral_for[_reward_token][_user] + new_claimable: uint256 = self.balanceOf[_user] * (integral - integral_for) / 10**18 + + return (self.claim_data[_user][_reward_token] >> 128) + new_claimable + + +@external +def claimable_tokens(addr: address) -> uint256: + """ + @notice Get the number of claimable tokens per user + @dev This function should be manually changed to "view" in the ABI + @return uint256 number of claimable tokens per user + """ + self._checkpoint(addr) + return self.integrate_fraction[addr] - Minter(MINTER).minted(addr, self) + + +@view +@external +def integrate_checkpoint() -> uint256: + """ + @notice Get the timestamp of the last checkpoint + """ + return self.period_timestamp[self.period] + + +@view +@external +def future_epoch_time() -> uint256: + """ + @notice Get the locally stored CRV future epoch start time + """ + return self.inflation_params >> 216 + + +@view +@external +def inflation_rate() -> uint256: + """ + @notice Get the locally stored CRV inflation rate + """ + return self.inflation_params % 2 ** 216 + + +@view +@external +def decimals() -> uint256: + """ + @notice Get the number of decimals for this token + @dev Implemented as a view method to reduce gas costs + @return uint256 decimal places + """ + return 18 + + +@view +@external +def version() -> String[8]: + """ + @notice Get the version of this gauge contract + """ + return VERSION + + +@view +@external +def DOMAIN_SEPARATOR() -> bytes32: + """ + @notice EIP712 domain separator. + """ + return self._domain_separator() diff --git a/contracts/mock/CallbackSwap.vy b/contracts/mock/CallbackSwap.vy new file mode 100644 index 00000000..edfc8960 --- /dev/null +++ b/contracts/mock/CallbackSwap.vy @@ -0,0 +1,113 @@ +# @version 0.3.9 + +""" +@title CurveExchangeExtendedDemo +@author fiddyresearch.eth +@notice A demo of a strategy execution that swaps on + Curve pools without granting an ERC20 approvals to the DEX contracts +@dev Only works with Curve Cryptoswap contracts that have `exchange_extended` + Does not do native token swaps (ETH <> whatever). +""" + +from vyper.interfaces import ERC20 + +interface Swap: + def exchange_extended( + i: uint256, + j: uint256, + dx: uint256, + min_dy: uint256, + use_eth: bool, + sender: address, + receiver: address, + cb: bytes32 + ) -> uint256: nonpayable + + +vault: public(immutable(address)) +keeper: public(immutable(address)) +whitelisted_pool: public(immutable(Swap)) + + +@external +def __init__( + _vault: address, + _whitelisted_pool: address, + _keeper: address +): + + vault = _vault + whitelisted_pool = Swap(_whitelisted_pool) + keeper = _keeper + + +@external +def transfer_callback( + sender: address, + receiver: address, + coin: address, + amount_to_transfer: uint256, + amount_to_receive: uint256, +): + """ + Curve CryptoSwap (factory only) pools expect the callback to have the inputs: + sender: address + receiver: address + coin: address + dx: uint256 + dy: uint256 + + The logic of how the callback is handled is: + + ```pool.internal._transfer_in(...): + + b: uint256 = ERC20(_coin).balanceOf(self) + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, _coin, dx, dy) + ) + ) + assert ERC20(_coin).balanceOf(self) - b == dx # dev: callback didn't give us coins + ``` + + The callback fn sig expects several inputs, but we only care about amount_to_transfer. + Everything else can be simply ignored: or you can use it to do more complex things in + the callback fn (checks etc.). + """ + assert msg.sender == whitelisted_pool.address + assert tx.origin == keeper + + ERC20(coin).transferFrom(vault, whitelisted_pool.address, amount_to_transfer) + + +@external +def callback_and_swap( + i: uint256, + j: uint256, + dx: uint256, + min_dy: uint256, +) -> uint256: + + assert msg.sender == keeper + + selector: uint256 = ( + convert( + method_id( + "transfer_callback(address,address,address,uint256,uint256)" + ), + uint256 + ) << 224 + ) + + return whitelisted_pool.exchange_extended( + i, # input coin index + j, # output coin index + dx, # amount in + min_dy, # minimum expected out + False, # use native token (eth) + msg.sender, # sender (doesnt matter because we set it to the vault in the callback) + vault, # receiver + convert(selector, bytes32) # <-- your callback is being called here + ) diff --git a/contracts/mock/CallbackTestZap.vy b/contracts/mock/CallbackTestZap.vy new file mode 100644 index 00000000..31cd6da0 --- /dev/null +++ b/contracts/mock/CallbackTestZap.vy @@ -0,0 +1,66 @@ +# @version ^0.3.7 + +from vyper.interfaces import ERC20 + +interface Swap: + def exchange_extended( + i: uint256, + j: uint256, + dx: uint256, + min_dy: uint256, + use_eth: bool, + sender: address, + receiver: address, + cb: bytes32 + ) -> uint256: nonpayable + +input_amount: public(uint256) +output_amount: public(uint256) +POOL: immutable(address) + + +@external +def __init__(_pool: address): + POOL = _pool + + +@external +def good_callback(sender: address, receiver: address, coin: address, dx: uint256, dy: uint256): + assert msg.sender == POOL + ERC20(coin).transferFrom(sender, POOL, dx) + + # Debug info only, not needed in practice + self.input_amount = dx + self.output_amount = dy + + +@external +def evil_callback(sender: address, receiver: address, coin: address, dx: uint256, dy: uint256): + assert msg.sender == POOL + # Transfer the saved imput amount, not dx, to fool the pool + ERC20(coin).transferFrom(sender, POOL, self.input_amount) + + # Debug info only, not needed in practice + self.input_amount = dx + self.output_amount = dy + + +@external +def set_evil_input_amount(x: uint256): + self.input_amount = x + + +@external +def good_exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256, use_eth: bool = False) -> uint256: + selector: uint256 = shift(convert(method_id("good_callback(address,address,address,uint256,uint256)"), uint256), 224) + return Swap(POOL).exchange_extended( + i, j, dx, min_dy, use_eth, msg.sender, msg.sender, + convert(selector, bytes32)) + + +@external +def evil_exchange(i: uint256, j: uint256, dx: uint256, min_dy: uint256, use_eth: bool = False) -> uint256: + selector: uint256 = shift(convert(method_id("evil_callback(address,address,address,uint256,uint256)"), uint256), 224) + return Swap(POOL).exchange_extended( + i, j, dx, min_dy, use_eth, msg.sender, msg.sender, + convert(selector, bytes32)) diff --git a/contracts/mock/ERC20Mock.vy b/contracts/mock/ERC20Mock.vy new file mode 100644 index 00000000..81f9c254 --- /dev/null +++ b/contracts/mock/ERC20Mock.vy @@ -0,0 +1,62 @@ +# @version ^0.3.7 + +""" +@notice Mock ERC20 for testing +""" + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) +balanceOf: public(HashMap[address, uint256]) +allowances: HashMap[address, HashMap[address, uint256]] +totalSupply: public(uint256) + + +@external +def __init__(_name: String[64], _symbol: String[32], _decimals: uint256): + self.name = _name + self.symbol = _symbol + self.decimals = _decimals + + +@external +@view +def allowance(_owner: address, _spender: address) -> uint256: + return self.allowances[_owner][_spender] + + +@external +def transfer(_to: address, _value: uint256) -> bool: + self.balanceOf[msg.sender] -= _value + self.balanceOf[_to] += _value + log Transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + self.allowances[_from][msg.sender] -= _value + log Transfer(_from, _to, _value) + return True + + +@external +def approve(_spender: address, _value: uint256) -> bool: + self.allowances[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + return True diff --git a/contracts/mock/WETH.vy b/contracts/mock/WETH.vy new file mode 100644 index 00000000..d7c94369 --- /dev/null +++ b/contracts/mock/WETH.vy @@ -0,0 +1,82 @@ +# @version ^0.3.7 + + +""" +@notice Mock ERC20 for testing +""" + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) +balanceOf: public(HashMap[address, uint256]) +allowances: HashMap[address, HashMap[address, uint256]] +totalSupply: public(uint256) + + +@payable +@external +def __init__(): + self.name = "Wrapped Ether" + self.symbol = "WETH" + self.decimals = 18 + + +@external +@view +def allowance(_owner: address, _spender: address) -> uint256: + return self.allowances[_owner][_spender] + + +@external +def transfer(_to: address, _value: uint256) -> bool: + self.balanceOf[msg.sender] -= _value + self.balanceOf[_to] += _value + log Transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + self.allowances[_from][msg.sender] -= _value + log Transfer(_from, _to, _value) + return True + + +@external +def approve(_spender: address, _value: uint256) -> bool: + self.allowances[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + return True + + +@payable +@external +def deposit(): + self.balanceOf[msg.sender] += msg.value + + +@payable +@external +def __default__(): + self.balanceOf[msg.sender] += msg.value + + +@external +def withdraw(_amount: uint256): + self.balanceOf[msg.sender] -= _amount + raw_call(msg.sender, b"", value=_amount) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py new file mode 100644 index 00000000..f947969c --- /dev/null +++ b/tests/fixtures/accounts.py @@ -0,0 +1,72 @@ +import boa +import pytest +from eth_account.account import Account + +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def deployer(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def owner(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def factory_admin(tricrypto_factory): + return tricrypto_factory.admin() + + +@pytest.fixture(scope="module") +def fee_receiver(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def user(): + acc = boa.env.generate_address() + boa.env.set_balance(acc, 10**25) + return acc + + +@pytest.fixture(scope="module") +def users(): + accs = [i() for i in [boa.env.generate_address] * 10] + for acc in accs: + boa.env.set_balance(acc, 10**25) + return accs + + +@pytest.fixture(scope="module") +def eth_acc(): + return Account.create() + + +@pytest.fixture(scope="module") +def alice(): + acc = boa.env.generate_address() + boa.env.set_balance(acc, 10**25) + return acc + + +@pytest.fixture(scope="module") +def loaded_alice(swap, alice): + mint_for_testing(swap, alice, 10**21) + return alice + + +@pytest.fixture(scope="module") +def bob(): + acc = boa.env.generate_address() + boa.env.set_balance(acc, 10**25) + return acc + + +@pytest.fixture(scope="module") +def charlie(): + acc = boa.env.generate_address() + boa.env.set_balance(acc, 10**25) + return acc diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py new file mode 100644 index 00000000..b81ce0e6 --- /dev/null +++ b/tests/fixtures/factory.py @@ -0,0 +1,13 @@ +import boa +import pytest + + +@pytest.fixture(scope="module") +def gauge_interface(): + return boa.load_partial("contracts/main/LiquidityGauge.vy") + + +@pytest.fixture(scope="module") +def gauge_implementation(deployer, gauge_interface): + with boa.env.prank(deployer): + return gauge_interface.deploy_as_blueprint() diff --git a/tests/fixtures/pool.py b/tests/fixtures/pool.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py new file mode 100644 index 00000000..bbde3a68 --- /dev/null +++ b/tests/fixtures/tokens.py @@ -0,0 +1,36 @@ +import boa +import pytest + + +@pytest.fixture(scope="module") +def weth(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/mocks/WETH.vy") + + +@pytest.fixture(scope="module") +def usdt(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/mocks/ERC20Mock.vy", "USDT", "USDT", 6) + + +@pytest.fixture(scope="module") +def dai(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/mocks/ERC20Mock.vy", "DAI", "DAI", 18) + + +@pytest.fixture(scope="module") +def steth(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/mocks/ERC20Mock.vy", "stETH", "stETH", 18) + + +@pytest.fixture(scope="module") +def plain_coins(usdt, dai): + yield [usdt, dai] + + +@pytest.fixture(scope="module") +def rebasing_coins(weth, steth): + yield [steth, weth] diff --git a/tests/utils/tokens.py b/tests/utils/tokens.py new file mode 100644 index 00000000..34633a90 --- /dev/null +++ b/tests/utils/tokens.py @@ -0,0 +1,17 @@ +import boa +from eth_utils import to_checksum_address + + +def mint_for_testing(token_contract, addr, amount, mint_eth=False): + + addr = to_checksum_address(addr) + + if token_contract.symbol() == "WETH": + boa.env.set_balance(addr, boa.env.get_balance(addr) + amount) + if not mint_eth: + with boa.env.prank(addr): + token_contract.deposit(value=amount) + else: + token_contract.eval(f"self.totalSupply += {amount}") + token_contract.eval(f"self.balanceOf[{addr}] += {amount}") + token_contract.eval(f"log Transfer(empty(address), {addr}, {amount})") From ca01bcffbe14f1d9a0fcdf3eb79eda9594993c68 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:03:45 +0200 Subject: [PATCH 004/337] feat: add meta impl, introduce create_from_blueprint --- contracts/main/CurveStableSwap2NG.vy | 145 +- contracts/main/CurveStableSwapFactoryNG.vy | 13 +- contracts/main/CurveStableSwapMetaNG.vy | 1601 ++++++++++++++++++++ 3 files changed, 1697 insertions(+), 62 deletions(-) create mode 100644 contracts/main/CurveStableSwapMetaNG.vy diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 3e2cb291..d795d2e7 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -97,66 +97,81 @@ event CommitNewFee: event ApplyNewFee: fee: uint256 -# ---------------------------- Storage Variables ----------------------------- + +# ---------------------------- Pool Parameters ------------------------------- WETH20: public(immutable(address)) +factory: public(address) +coins: public(address[N_COINS]) N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -ADMIN_FEE: constant(uint256) = 5000000000 +# ---------------------- Pool Amplification Parameters ----------------------- A_PRECISION: constant(uint256) = 100 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 MAX_A: constant(uint256) = 10 ** 6 MAX_A_CHANGE: constant(uint256) = 10 -MIN_RAMP_TIME: constant(uint256) = 86400 - -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") -PERMIT_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") -# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 -ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 -VERSION: constant(String[8]) = "v7.0.0" - -# shift(2**32 - 1, 224) -ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 +initial_A: public(uint256) +future_A: public(uint256) +initial_A_time: public(uint256) +future_A_time: public(uint256) +# ---------------------------- Fee Variables --------------------------------- -factory: public(address) +ADMIN_FEE: constant(uint256) = 5000000000 -coins: public(address[N_COINS]) -admin_balances: public(uint256[N_COINS]) +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 fee: public(uint256) # fee * 1e10 future_fee: public(uint256) + +# ---------------------------- Admin Variables ------------------------------- + +MIN_RAMP_TIME: constant(uint256) = 86400 +ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 admin_action_deadline: public(uint256) -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) +admin_balances: public(uint256[N_COINS]) + +# ----------------------- Oracle Specific vars ------------------------------- rate_multipliers: uint256[N_COINS] # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: uint256[N_COINS] -name: public(String[64]) -symbol: public(String[32]) +last_prices_packed: uint256 # [last_price, ma_price] +ma_exp_time: public(uint256) +ma_last_time: public(uint256) + +# shift(2**32 - 1, 224) +ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 + +# ----------------------- ERC20 Specific vars -------------------------------- + +name: public(immutable(String[64])) +symbol: public(immutable(String[32])) +decimals: public(constant(uint8)) = 18 +version: public(constant(String[8])) = "v7.0.0" balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) totalSupply: public(uint256) - -DOMAIN_SEPARATOR: public(bytes32) nonces: public(HashMap[address, uint256]) -last_prices_packed: uint256 # [last_price, ma_price] -ma_exp_time: public(uint256) -ma_last_time: public(uint256) +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +VERSION_HASH: constant(bytes32) = keccak256(version) +NAME_HASH: immutable(bytes32) +CACHED_CHAIN_ID: immutable(uint256) +salt: public(immutable(bytes32)) +CACHED_DOMAIN_SEPARATOR: immutable(bytes32) # ------------------------------ AMM Setup ----------------------------------- @@ -200,6 +215,9 @@ def __init__( WETH20 = _weth + name = concat("Curve.fi Factory Plain Pool: ", _name) + symbol = concat(_symbol, "-f") + for i in range(N_COINS): coin: address = _coins[i] @@ -221,12 +239,19 @@ def __init__( self.last_prices_packed = self.pack_prices(10**18, 10**18) self.ma_last_time = block.timestamp - name: String[64] = concat("Curve.fi Factory Plain Pool: ", _name) - self.name = name - self.symbol = concat(_symbol, "-f") - - self.DOMAIN_SEPARATOR = keccak256( - _abi_encode(EIP712_TYPEHASH, keccak256(name), keccak256(VERSION), chain.id, self) + # EIP712 + NAME_HASH = keccak256(name) + salt = block.prevhash + CACHED_CHAIN_ID = chain.id + CACHED_DOMAIN_SEPARATOR = keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) ) # fire a transfer event so block explorers identify the contract as an ERC20 @@ -724,6 +749,8 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- + # `dx` that `_transfer_in` gives is whatever the pool received after ERC20 + # transfer dx: uint256 = self._transfer_in( coins[i], _dx, _min_dy, mvalue, callbacker, callback_sig, @@ -761,7 +788,7 @@ def _exchange( # ------------------------------------------------------------------------ - log TokenExchange(msg.sender, i, _dx, j, dy) # <--- TODO: which dx in the event? + log TokenExchange(msg.sender, i, _dx, j, dy) return dy @@ -1176,16 +1203,21 @@ def exp(x: int256) -> uint256: # --------------------------- ERC20 Functionality ---------------------------- - @view -@external -def decimals() -> uint8: - """ - @notice Get the number of decimals for this token - @dev Implemented as a view method to reduce gas costs - @return uint8 decimal places - """ - return 18 +@internal +def _domain_separator() -> bytes32: + if chain.id != CACHED_CHAIN_ID: + return keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + return CACHED_DOMAIN_SEPARATOR @internal @@ -1276,8 +1308,8 @@ def permit( digest: bytes32 = keccak256( concat( b"\x19\x01", - self.DOMAIN_SEPARATOR, - keccak256(_abi_encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) + self._domain_separator(), + keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) ) ) @@ -1295,6 +1327,16 @@ def permit( return True +@view +@external +def DOMAIN_SEPARATOR() -> bytes32: + """ + @notice EIP712 domain separator. + @return bytes32 Domain Separator set for the current chain. + """ + return self._domain_separator() + + # ------------------------- AMM View Functions ------------------------------- @@ -1487,15 +1529,6 @@ def get_balances() -> uint256[N_COINS]: return self._balances() -@pure -@external -def version() -> String[8]: - """ - @notice Get the version of this token contract - """ - return VERSION - - @view @external def oracle(_idx: uint256) -> address: diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 7afa18a1..e9fc9511 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -579,9 +579,8 @@ def deploy_plain_pool( implementation: address = self.plain_implementations[n_coins][_implementation_idx] assert implementation != empty(address), "Invalid implementation index" - pool: address = create_minimal_proxy_to(implementation) - - CurvePlainPool(pool).initialize( + pool: address = create_from_blueprint( + implementation, _name, _symbol, _coins, @@ -591,7 +590,8 @@ def deploy_plain_pool( WETH20, _ma_exp_time, _method_ids, - _oracles + _oracles, + code_offset=3 ) length: uint256 = self.pool_count @@ -670,8 +670,10 @@ def deploy_metapool( decimals: uint256 = ERC20(_coin).decimals() assert decimals < 19, "Max 18 decimals for coins" + # TODO: the following needs an implementaiton contract AND create_from_blueprint pool: address = create_minimal_proxy_to(implementation) CurvePool(pool).initialize(_name, _symbol, _coin, 10 ** (36 - decimals), _A, _fee) + ERC20(_coin).approve(pool, max_value(uint256)) # add pool to pool_list @@ -718,8 +720,7 @@ def deploy_gauge(_pool: address) -> address: implementation: address = self.gauge_implementation assert implementation != empty(address), "Gauge implementation not set" - gauge: address = create_minimal_proxy_to(implementation) - LiquidityGauge(gauge).initialize(_pool) + gauge: address = create_from_blueprint(self.gauge_implementation, _pool, code_offset=3) self.pool_data[_pool].liquidity_gauge = gauge log LiquidityGaugeDeployed(_pool, gauge) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy new file mode 100644 index 00000000..5c15a4d0 --- /dev/null +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -0,0 +1,1601 @@ +# @version 0.3.9 +# TODO: convert this to Meta implementation!!!!! +""" +@title CurveStableSwapMetaNG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice 2 coin pool implementation with no lending, i.e. tokens are not + deposited into lending markets +@dev ERC20 support for return True/revert, return True/False, return None + ERC20 tokens can have arbitrary decimals (<=18). + Additional features include: + 1. Support for positive-rebasing and fee-on-transfer tokens + 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + 3. Support for ETH/WETH transfers + 4. Adds oracles for coin[1] w.r.t coin[0] + 5. Adds exchanging tokens with callbacks that allows for: + a. reduced ERC20 token transfers in zap contracts + b. swaps without transferFrom (no need for token approvals) +""" + +from vyper.interfaces import ERC20 + +implements: ERC20 + +# ------------------------------- Interfaces --------------------------------- + +interface Factory: + def convert_fees() -> bool: nonpayable + def get_fee_receiver(_pool: address) -> address: view + def admin() -> address: view + +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + +interface WETH: + def deposit(): payable + def withdraw(_amount: uint256): nonpayable + +# --------------------------------- Events ----------------------------------- + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + +event TokenExchange: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event AddLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event RemoveLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + token_supply: uint256 + +event RemoveLiquidityOne: + provider: indexed(address) + token_id: int128 + token_amount: uint256 + coin_amount: uint256 + token_supply: uint256 + +event RemoveLiquidityImbalance: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event RampA: + old_A: uint256 + new_A: uint256 + initial_time: uint256 + future_time: uint256 + +event StopRampA: + A: uint256 + t: uint256 + +event CommitNewFee: + new_fee: uint256 + +event ApplyNewFee: + fee: uint256 + + +# ---------------------------- Pool Parameters ------------------------------- + +WETH20: public(immutable(address)) +factory: public(address) + +coins: public(address[N_COINS]) + +N_COINS: constant(uint256) = 2 +N_COINS_128: constant(int128) = 2 +PRECISION: constant(uint256) = 10 ** 18 + +# ---------------------- Pool Amplification Parameters ----------------------- + +A_PRECISION: constant(uint256) = 100 +MAX_A: constant(uint256) = 10 ** 6 +MAX_A_CHANGE: constant(uint256) = 10 + +initial_A: public(uint256) +future_A: public(uint256) +initial_A_time: public(uint256) +future_A_time: public(uint256) + +# ---------------------------- Fee Variables --------------------------------- + +ADMIN_FEE: constant(uint256) = 5000000000 + +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 +fee: public(uint256) # fee * 1e10 +future_fee: public(uint256) + +# ---------------------------- Admin Variables ------------------------------- + +MIN_RAMP_TIME: constant(uint256) = 86400 +ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 +admin_action_deadline: public(uint256) + +admin_balances: public(uint256[N_COINS]) + +# ----------------------- Oracle Specific vars ------------------------------- + +rate_multipliers: uint256[N_COINS] +# [bytes4 method_id][bytes8 ][bytes20 oracle] +oracles: uint256[N_COINS] + +last_prices_packed: uint256 # [last_price, ma_price] +ma_exp_time: public(uint256) +ma_last_time: public(uint256) + +# shift(2**32 - 1, 224) +ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 + +# ----------------------- ERC20 Specific vars -------------------------------- + +name: public(immutable(String[64])) +symbol: public(immutable(String[32])) +decimals: public(constant(uint8)) = 18 +version: public(constant(String[8])) = "v7.0.0" + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) +nonces: public(HashMap[address, uint256]) + +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +VERSION_HASH: constant(bytes32) = keccak256(version) +NAME_HASH: immutable(bytes32) +CACHED_CHAIN_ID: immutable(uint256) +salt: public(immutable(bytes32)) +CACHED_DOMAIN_SEPARATOR: immutable(bytes32) + + +# ------------------------------ AMM Setup ----------------------------------- + + +@external +def __init__( + _name: String[32], + _symbol: String[10], + _coins: address[4], + _rate_multipliers: uint256[4], + _A: uint256, + _fee: uint256, + _weth: address, + _ma_exp_time: uint256, + _method_ids: bytes4[4], + _oracles: address[4], +): + """ + @notice Initialize the pool contract + @param _name Name of the new plain pool + @param _symbol Symbol for the new plain pool - will be + concatenated with factory symbol + @param _coins List of addresses of the coins being used in the pool. + @param _A Amplification co-efficient - a lower value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + """ + + WETH20 = _weth + + name = concat("Curve.fi Factory Plain Pool: ", _name) + symbol = concat(_symbol, "-f") + + for i in range(N_COINS): + + coin: address = _coins[i] + if coin == empty(address): + break + + self.coins[i] = coin + self.rate_multipliers[i] = _rate_multipliers[i] + self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + + A: uint256 = _A * A_PRECISION + self.initial_A = A + self.future_A = A + self.fee = _fee + self.factory = msg.sender + + assert _ma_exp_time != 0 + self.ma_exp_time = _ma_exp_time + self.last_prices_packed = self.pack_prices(10**18, 10**18) + self.ma_last_time = block.timestamp + + # EIP712 + NAME_HASH = keccak256(name) + salt = block.prevhash + CACHED_CHAIN_ID = chain.id + CACHED_DOMAIN_SEPARATOR = keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + + # fire a transfer event so block explorers identify the contract as an ERC20 + log Transfer(empty(address), self, 0) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert _ma_exp_time != 0 + + self.ma_exp_time = _ma_exp_time + + +# ------------------ Token transfers in and out of the AMM ------------------- + +@payable +@external +def __default__(): + if msg.value > 0: + assert WETH20 in self.coins + + +@internal +def _transfer_in( + coin: address, + dx: uint256, + dy: uint256, + mvalue: uint256, + callbacker: address, + callback_sig: bytes32, + sender: address, + receiver: address, + use_eth: bool, +) -> uint256: + """ + @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig` + if it is not empty. + @dev The callback sig must have the following args: + sender: address + receiver: address + coin: address + dx: uint256 + dy: uint256 + The `dy` that the pool enforces is actually min_dy. + Callback only occurs for `exchange_extended`. + Callback cannot happen for `_use_eth` = True. + @dev If callback_sig is empty, `_transfer_in` does a transferFrom. + @params _coin address of the coin to transfer in. + @params dx amount of `_coin` to transfer into the pool. + @params dy amount of `_coin` to transfer out of the pool. + @params mvalue msg.value if the transfer is ETH, 0 otherwise. + @params callbacker address to call `callback_sig` on. + @params callback_sig signature of the callback function. + @params sender address to transfer `_coin` from. + @params receiver address to transfer `_coin` to. + @params use_eth True if the transfer is ETH, False otherwise. + """ + _dx: uint256 = dx + + if use_eth and coin == WETH20: # <----------- Pool receives native token. + + assert mvalue == _dx # dev: incorrect eth amount + + else: # <------- Pool receives wrapped native token and not native token. + + assert mvalue == 0 # dev: nonzero eth amount + + initial_x: uint256 = ERC20(coin).balanceOf(self) + + # --------------------- Start Callback Handling ---------------------- + + if callback_sig == empty(bytes32): + + assert ERC20(coin).transferFrom( + sender, self, _dx, default_return_value=True + ) + + else: + + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, coin, _dx, dy) + ) + ) + + # If the coin is a fee-on-transfer token, transferring `_dx` amount can + # result in the pool receiving slightly less amount. So: recalculate dx + + _dx = ERC20(coin).balanceOf(self) - initial_x + + assert _dx > 0 # dev: pool received 0 tokens + + # -------------------- End Callback Handling ------------------------- + + if coin == WETH20: + WETH(WETH20).withdraw(_dx) # <--------- if WETH was transferred in + # previous step and `not use_eth`, withdraw WETH to ETH. + + # Return _dx so it can be used by `_exchange` and `add_liquidity`. + return _dx + + +@internal +def _transfer_out( + _coin: address, _amount: uint256, use_eth: bool, receiver: address +): + """ + @notice Transfer a single token from the pool to receiver. + @dev This function is called by `remove_liquidity` and + `remove_liquidity_one` and `_exchange` methods. + @params _coin Address of the token to transfer out + @params _amount Amount of token to transfer out + @params use_eth Whether to transfer ETH or not + @params receiver Address to send the tokens to + """ + + if use_eth and _coin == WETH20: + raw_call(receiver, b"", value=_amount) + else: + if _coin == WETH20: + WETH(WETH20).deposit(value=_amount) + + assert ERC20(_coin).transfer( + receiver, _amount, default_return_value=True + ) + + +# -------------------------- AMM Main Functions ------------------------------ + + +@payable +@external +@nonreentrant('lock') +def exchange( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + Allows for native token swaps (e.g. ETH <> whatever) + If native token is not in coin list and msg.value > 0, swap will revert + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + return self._exchange( + msg.sender, + msg.value, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32) + ) + + +@external +@nonreentrant('lock') +def exchange_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _sender: address, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + Not payable (does not accept eth) + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: No callback specified + return self._exchange( + _sender, + 0, # mvalue is zero here + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + msg.sender, # <---------------------------- callbacker is msg.sender. + _cb + ) + + +@payable +@external +@nonreentrant('lock') +def add_liquidity( + _amounts: uint256[N_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + coins: address[N_COINS] = self.coins + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + + for i in range(N_COINS): + + if _amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + msg.value, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + _use_eth + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + False + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[_receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), _receiver, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount + + +@external +@nonreentrant('lock') +def remove_liquidity_one_coin( + _burn_amount: uint256, + i: int128, + _min_received: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Withdraw a single coin from the pool + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_received Minimum amount of coin to receive + @param _receiver Address that receives the withdrawn coins + @return Amount of coin received + """ + dy: uint256[3] = self._calc_withdraw_one_coin(_burn_amount, i) + assert dy[0] >= _min_received, "Not enough coins removed" + + self.admin_balances[i] += dy[1] * ADMIN_FEE / FEE_DENOMINATOR + total_supply: uint256 = self.totalSupply - _burn_amount + self.totalSupply = total_supply + self.balanceOf[msg.sender] -= _burn_amount + + log Transfer(msg.sender, empty(address), _burn_amount) + + self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) + + self.save_p_from_price(dy[2]) + + return dy[0] + + +@external +@nonreentrant('lock') +def remove_liquidity_imbalance( + _amounts: uint256[N_COINS], + _max_burn_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the withdrawn coins + @return Actual amount of the LP token burned in the withdrawal + """ + amp: uint256 = self._A() + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + coins: address[N_COINS] = self.coins + + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + amount: uint256 = _amounts[i] + if amount != 0: + new_balances[i] -= amount + + self._transfer_out(coins[i], amount, _use_eth, _receiver) + + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + D2: uint256 = self.get_D_mem(rates, new_balances, amp) + + self.save_p(new_balances, amp, D2) + + total_supply: uint256 = self.totalSupply + burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 + assert burn_amount > 1 # dev: zero tokens burned + assert burn_amount <= _max_burn_amount, "Slippage screwed you" + + total_supply -= burn_amount + self.totalSupply = total_supply + self.balanceOf[msg.sender] -= burn_amount + log Transfer(msg.sender, empty(address), burn_amount) + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + + return burn_amount + + +@external +@nonreentrant('lock') +def remove_liquidity( + _burn_amount: uint256, + _min_amounts: uint256[N_COINS], + _use_eth: bool = False, + _receiver: address = msg.sender, + _claim_admin_fees: bool = True, +) -> uint256[N_COINS]: + """ + @notice Withdraw coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the withdrawn coins + @return List of amounts of coins that were withdrawn + """ + total_supply: uint256 = self.totalSupply + amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + balances: uint256[N_COINS] = self._balances() + coins: address[N_COINS] = self.coins + + for i in range(N_COINS): + value: uint256 = balances[i] * _burn_amount / total_supply + assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" + amounts[i] = value + + self._transfer_out(coins[i], value, _use_eth, _receiver) + + total_supply -= _burn_amount + self.balanceOf[msg.sender] -= _burn_amount + self.totalSupply = total_supply + log Transfer(msg.sender, empty(address), _burn_amount) + + log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply) + + # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. + if _claim_admin_fees: + self._withdraw_admin_fees() + + return amounts + + +@external +def withdraw_admin_fees(): + """ + @notice Claim admin fees. Callable by anyone. + """ + self._withdraw_admin_fees() + + +# ------------------------ AMM Internal Functions ---------------------------- + + +@internal +def _exchange( + sender: address, + mvalue: uint256, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + use_eth: bool, + receiver: address, + callbacker: address, + callback_sig: bytes32 +) -> uint256: + + assert i != j # dev: coin index out of range + assert _dx > 0 # dev: do not exchange 0 coins + + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) + coins: address[N_COINS] = self.coins + + # --------------------------- Do Transfer in ----------------------------- + + # `dx` that `_transfer_in` gives is whatever the pool received after ERC20 + # transfer + dx: uint256 = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) + + # ------------------------------------------------------------------------ + + x: uint256 = xp[i] + dx * rates[i] / PRECISION + + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(i, j, x, xp, amp, D) + + dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" + + self.admin_balances[j] += ( + dy_fee * ADMIN_FEE / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # xp is not used anymore, so we reuse it for price calc + xp[i] = x + xp[j] = y + # D is not changed because we did not apply a fee + self.save_p(xp, amp, D) + + # --------------------------- Do Transfer out ---------------------------- + + self._transfer_out(coins[j], dy, use_eth, receiver) + + # ------------------------------------------------------------------------ + + log TokenExchange(msg.sender, i, _dx, j, dy) + + return dy + + +@view +@internal +def _stored_rates() -> uint256[N_COINS]: + + rates: uint256[N_COINS] = self.rate_multipliers + + for i in range(N_COINS): + oracle: uint256 = self.oracles[i] + if oracle == 0: + continue + + # NOTE: assumed that response is of precision 10**18 + response: Bytes[32] = raw_call( + convert(oracle % 2**160, address), + _abi_encode(oracle & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ) + + assert len(response) != 0 + rates[1] = rates[1] * convert(response, uint256) / PRECISION + + return rates + + +@view +@internal +def _balances() -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + return result + + +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@pure +@internal +def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = _rates[i] * _balances[i] / PRECISION + return result + + +@pure +@internal +def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for i in range(255): + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + Dprev: uint256 = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@view +@internal +def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: + xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + +@view +@internal +def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, _D: uint256) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + amp: uint256 = _amp + D: uint256 = _D + if _D == 0: + amp = self._A() + D = self.get_D(xp, amp) + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(N_COINS_128): + if _i == i: + _x = x + elif _i != j: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@pure +@internal +def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(N_COINS_128): + if _i != i: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + amp: uint256 = self._A() + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + D0: uint256 = self.get_D(xp, amp) + + total_supply: uint256 = self.totalSupply + D1: uint256 = D0 - _burn_amount * D0 / total_supply + new_y: uint256 = self.get_y_D(amp, i, xp, D1) + + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: uint256[N_COINS] = empty(uint256[N_COINS]) + + for j in range(N_COINS_128): + dx_expected: uint256 = 0 + xp_j: uint256 = xp[j] + if j == i: + dx_expected = xp_j * D1 / D0 - new_y + else: + dx_expected = xp_j - xp_j * D1 / D0 + xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) + dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees + dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + + xp[i] = new_y + last_p: uint256 = 0 + if new_y > 0: + last_p = self._get_p(xp, amp, D1) + + return [dy, dy_0 - dy, last_p] + + +@internal +def _withdraw_admin_fees(): + receiver: address = Factory(self.factory).get_fee_receiver(self) + + for i in range(N_COINS): + amount: uint256 = self.admin_balances[i] + + if amount > 0: + + if self.coins[i] == WETH20: + raw_call(receiver, b"", value=amount) + else: + assert ERC20(self.coins[i]).transfer( + receiver, + amount, + default_return_value=True + ) + + self.admin_balances = empty(uint256[N_COINS]) + + +# -------------------------- AMM Price Methods ------------------------------- + + +@pure +@internal +def pack_prices(p1: uint256, p2: uint256) -> uint256: + assert p1 < 2**128 + assert p2 < 2**128 + return p1 | (p2 << 128) + + +@internal +@view +def _get_p(xp: uint256[N_COINS], amp: uint256, D: uint256) -> uint256: + # dx_0 / dx_1 only, however can have any number of coins in pool + ANN: uint256 = amp * N_COINS + Dr: uint256 = D / (N_COINS**N_COINS) + for i in range(N_COINS): + Dr = Dr * D / xp[i] + return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) + + +@internal +def save_p_from_price(last_price: uint256): + """ + Saves current price and its EMA + """ + if last_price != 0: + self.last_prices_packed = self.pack_prices(last_price, self._ma_price()) + if self.ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp + + +@internal +def save_p(xp: uint256[N_COINS], amp: uint256, D: uint256): + """ + Saves current price and its EMA + """ + self.save_p_from_price(self._get_p(xp, amp, D)) + + +@internal +@view +def _ma_price() -> uint256: + ma_last_time: uint256 = self.ma_last_time + + pp: uint256 = self.last_prices_packed + last_price: uint256 = pp & (2**128 - 1) + last_ema_price: uint256 = (pp >> 128) + + if ma_last_time < block.timestamp: + alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) + return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 + + else: + return last_ema_price + + +# ----------------------------- Math Utils ----------------------------------- + + +@internal +@pure +def exp(x: int256) -> uint256: + + """ + @dev Calculates the natural exponential function of a signed integer with + a precision of 1e18. + @notice Note that this function consumes about 810 gas units. The implementation + is inspired by Remco Bloemen's implementation under the MIT license here: + https://xn--2-umb.com/22/exp-ln. + @dev This implementation is derived from Snekmate, which is authored + by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. + https://github.com/pcaversaccio/snekmate + @param x The 32-byte variable. + @return int256 The 32-byte calculation result. + """ + value: int256 = x + + # If the result is `< 0.5`, we return zero. This happens when we have the following: + # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". + if (x <= -42139678854452767551): + return empty(uint256) + + # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. + # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". + assert x < 135305999368893231589, "wad_exp overflow" + + # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher + # intermediate precision and a binary base. This base conversion is a multiplication with + # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". + value = unsafe_div(x << 78, 5 ** 18) + + # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two + # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives + # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". + k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 + value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) + + # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, + # we will multiply by a scaling factor later. + y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) + p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ + 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) + + # We leave `p` in the "2 ** 192" base so that we do not have to scale it up + # again for the division. + q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) + q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) + q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) + + # The polynomial `q` has no zeros in the range because all its roots are complex. + # No scaling is required, as `p` is already "2 ** 96" too large. Also, + # `r` is in the range "(0.09, 0.25) * 2**96" after the division. + r: int256 = unsafe_div(p, q) + + # To finalise the calculation, we have to multiply `r` by: + # - the scale factor "s = ~6.031367120", + # - the factor "2 ** k" from the range reduction, and + # - the factor "1e18 / 2 ** 96" for the base conversion. + # We do this all at once, with an intermediate result in "2**213" base, + # so that the final right shift always gives a positive value. + + # Note that to circumvent Vyper's safecast feature for the potentially + # negative parameter value `r`, we first convert `r` to `bytes32` and + # subsequently to `uint256`. Remember that the EVM default behaviour is + # to use two's complement representation to handle signed integers. + return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) + + +# --------------------------- ERC20 Functionality ---------------------------- + +@view +@internal +def _domain_separator() -> bytes32: + if chain.id != CACHED_CHAIN_ID: + return keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + return CACHED_DOMAIN_SEPARATOR + + +@internal +def _transfer(_from: address, _to: address, _value: uint256): + # # NOTE: vyper does not allow underflows + # # so the following subtraction would revert on insufficient balance + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + + log Transfer(_from, _to, _value) + + +@external +def transfer(_to : address, _value : uint256) -> bool: + """ + @dev Transfer token for a specified address + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + self._transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from : address, _to : address, _value : uint256) -> bool: + """ + @dev Transfer tokens from one address to another. + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + self._transfer(_from, _to, _value) + + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != max_value(uint256): + self.allowance[_from][msg.sender] = _allowance - _value + + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk that + someone may use both the old and new allowance by unfortunate transaction + ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + + log Approval(msg.sender, _spender, _value) + return True + + +@external +def permit( + _owner: address, + _spender: address, + _value: uint256, + _deadline: uint256, + _v: uint8, + _r: bytes32, + _s: bytes32 +) -> bool: + """ + @notice Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 + @dev Supports smart contract wallets which implement ERC1271 + https://eips.ethereum.org/EIPS/eip-1271 + @param _owner The address which is a source of funds and has signed the Permit. + @param _spender The address which is allowed to spend the funds. + @param _value The amount of tokens to be spent. + @param _deadline The timestamp after which the Permit is no longer valid. + @param _v The bytes[64] of the valid secp256k1 signature of permit by owner + @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner + @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner + @return True, if transaction completes successfully + """ + assert _owner != empty(address) + assert block.timestamp <= _deadline + + nonce: uint256 = self.nonces[_owner] + digest: bytes32 = keccak256( + concat( + b"\x19\x01", + self._domain_separator(), + keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) + ) + ) + + if _owner.is_contract: + sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) + # reentrancy not a concern since this is a staticcall + assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL + else: + assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner + + self.allowance[_owner][_spender] = _value + self.nonces[_owner] = nonce + 1 + + log Approval(_owner, _spender, _value) + return True + + +@view +@external +def DOMAIN_SEPARATOR() -> bytes32: + """ + @notice EIP712 domain separator. + @return bytes32 Domain Separator set for the current chain. + """ + return self._domain_separator() + + +# ------------------------- AMM View Functions ------------------------------- + + +@view +@external +def last_price() -> uint256: + return self.last_prices_packed & (2**128 - 1) + + +@view +@external +def ema_price() -> uint256: + return (self.last_prices_packed >> 128) + + +@external +@view +def get_p() -> uint256: + amp: uint256 = self._A() + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = self.get_D(xp, amp) + return self._get_p(xp, amp, D) + + +@external +@view +@nonreentrant('lock') +def price_oracle() -> uint256: + return self._ma_price() + + +@view +@external +def get_dx(i: int128, j: int128, dy: uint256) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + + y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee) + x: uint256 = self.get_y(j, i, y, xp, 0, 0) + return (x - xp[i]) * PRECISION / rates[i] + + +@view +@external +def get_dy(i: int128, j: int128, dx: uint256) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + + x: uint256 = xp[i] + (dx * rates[i] / PRECISION) + y: uint256 = self.get_y(i, j, x, xp, 0, 0) + dy: uint256 = xp[j] - y - 1 + fee: uint256 = self.fee * dy / FEE_DENOMINATOR + return (dy - fee) * PRECISION / rates[j] + + +@view +@external +def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing a single coin + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @return Amount of coin received + """ + return self._calc_withdraw_one_coin(_burn_amount, i)[0] + + +@view +@external +@nonreentrant('lock') +def get_virtual_price() -> uint256: + """ + @notice The current virtual price of the pool LP token + @dev Useful for calculating profits + @return LP token virtual price normalized to 1e18 + """ + amp: uint256 = self._A() + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = self.get_D(xp, amp) + # D is in the units similar to DAI (e.g. converted to precision 1e18) + # When balanced, D = n * x_u - total virtual value of the portfolio + return D * PRECISION / self.totalSupply + + +@view +@external +def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + amount: uint256 = _amounts[i] + if _is_deposit: + new_balances[i] += amount + else: + new_balances[i] -= amount + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + D2: uint256 = D1 + if total_supply > 0: + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2 = self.get_D(xp, amp) + else: + return D1 # Take the dust if there was any + + diff: uint256 = 0 + if _is_deposit: + diff = D2 - D0 + else: + diff = D0 - D2 + return diff * total_supply / D0 + + +@view +@external +def admin_fee() -> uint256: + return ADMIN_FEE + + +@view +@external +def A() -> uint256: + return self._A() / A_PRECISION + + +@view +@external +def A_precise() -> uint256: + return self._A() + + +@view +@external +def balances(i: uint256) -> uint256: + """ + @notice Get the current balance of a coin within the + pool, less the accrued admin fees + @param i Index value for the coin to query balance of + @return Token balance + """ + return self._balances()[i] + + +@view +@external +def get_balances() -> uint256[N_COINS]: + return self._balances() + + +@view +@external +def oracle(_idx: uint256) -> address: + if _idx < N_COINS: + return convert(self.oracles[_idx] % 2**160, address) + return empty(address) + + +# --------------------------- AMM Admin Functions ---------------------------- + + +@external +def ramp_A(_future_A: uint256, _future_time: uint256): + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME + assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time + + _initial_A: uint256 = self._A() + _future_A_p: uint256 = _future_A * A_PRECISION + + assert _future_A > 0 and _future_A < MAX_A + if _future_A_p < _initial_A: + assert _future_A_p * MAX_A_CHANGE >= _initial_A + else: + assert _future_A_p <= _initial_A * MAX_A_CHANGE + + self.initial_A = _initial_A + self.future_A = _future_A_p + self.initial_A_time = block.timestamp + self.future_A_time = _future_time + + log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) + + +@external +def stop_ramp_A(): + assert msg.sender == Factory(self.factory).admin() # dev: only owner + + current_A: uint256 = self._A() + self.initial_A = current_A + self.future_A = current_A + self.initial_A_time = block.timestamp + self.future_A_time = block.timestamp + # now (block.timestamp < t1) is always False, so we return saved A + + log StopRampA(current_A, block.timestamp) + + +@external +def commit_new_fee(_new_fee: uint256): + assert msg.sender == Factory(self.factory).admin() + assert _new_fee <= MAX_FEE + assert self.admin_action_deadline == 0 + + self.future_fee = _new_fee + self.admin_action_deadline = block.timestamp + ADMIN_ACTIONS_DEADLINE_DT + log CommitNewFee(_new_fee) + + +@external +def apply_new_fee(): + assert msg.sender == Factory(self.factory).admin() + deadline: uint256 = self.admin_action_deadline + assert deadline != 0 and block.timestamp >= deadline + + fee: uint256 = self.future_fee + self.fee = fee + self.admin_action_deadline = 0 + log ApplyNewFee(fee) From e4e0e1d4e09c1d761a10e92a4b5110311668a3f7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 22 Jun 2023 15:45:42 +0200 Subject: [PATCH 005/337] pools created; todos added; --- contracts/main/CurveStableSwapFactoryNG.vy | 8 +- contracts/mock/{ERC20Mock.vy => ERC20.vy} | 0 contracts/mock/ERC20Oracle.vy | 81 ++++++++++++++++ tests/conftest.py | 6 ++ tests/fixtures/factory.py | 52 +++++++++++ tests/fixtures/pool.py | 0 tests/fixtures/pools.py | 103 +++++++++++++++++++++ tests/fixtures/tokens.py | 33 ++++++- 8 files changed, 276 insertions(+), 7 deletions(-) rename contracts/mock/{ERC20Mock.vy => ERC20.vy} (100%) create mode 100644 contracts/mock/ERC20Oracle.vy delete mode 100644 tests/fixtures/pool.py create mode 100644 tests/fixtures/pools.py diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index e9fc9511..b9422a20 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -148,12 +148,12 @@ market_counts: HashMap[uint256, uint256] @external -def __init__(_fee_receiver: address, _weth: address): +def __init__(_fee_receiver: address, _owner: address, _weth: address): - WETH20 = _weth - - self.admin = msg.sender self.fee_receiver = _fee_receiver + self.admin = _owner + + WETH20 = _weth # <--- Factory Getters ---> diff --git a/contracts/mock/ERC20Mock.vy b/contracts/mock/ERC20.vy similarity index 100% rename from contracts/mock/ERC20Mock.vy rename to contracts/mock/ERC20.vy diff --git a/contracts/mock/ERC20Oracle.vy b/contracts/mock/ERC20Oracle.vy new file mode 100644 index 00000000..bdd0f5a4 --- /dev/null +++ b/contracts/mock/ERC20Oracle.vy @@ -0,0 +1,81 @@ +# @version ^0.3.7 + +""" +@notice Mock ERC20 for testing +""" + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) +balanceOf: public(HashMap[address, uint256]) +allowances: HashMap[address, HashMap[address, uint256]] +totalSupply: public(uint256) + +exchange_rate: immutable(uint256) + + +@external +def __init__( + _name: String[64], + _symbol: String[32], + _decimals: uint256, + _exchange_rate: uint256 +): + + self.name = _name + self.symbol = _symbol + + assert _decimals == 18, "Decimals must be 18" + self.decimals = _decimals + + exchange_rate = _exchange_rate + + +@external +@view +def allowance(_owner: address, _spender: address) -> uint256: + return self.allowances[_owner][_spender] + + +@external +def transfer(_to: address, _value: uint256) -> bool: + self.balanceOf[msg.sender] -= _value + self.balanceOf[_to] += _value + log Transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + self.allowances[_from][msg.sender] -= _value + log Transfer(_from, _to, _value) + return True + + +@external +def approve(_spender: address, _value: uint256) -> bool: + self.allowances[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + return True + + +@external +@view +def exchangeRate() -> uint256: + # some arbitrary exchange rate: + return exchange_rate diff --git a/tests/conftest.py b/tests/conftest.py index e69de29b..73ed2fda 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +pytest_plugins = [ + "tests.fixtures.accounts", + "tests.fixtures.tokens", + "tests.fixtures.pools", + "tests.fixtures.factory", +] diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index b81ce0e6..1e89d9e0 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -11,3 +11,55 @@ def gauge_interface(): def gauge_implementation(deployer, gauge_interface): with boa.env.prank(deployer): return gauge_interface.deploy_as_blueprint() + + +@pytest.fixture(scope="module") +def amm_interface_plain(): + return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") + + +@pytest.fixture(scope="module") +def amm_implementation_plain(deployer, amm_interface_plain): + with boa.env.prank(deployer): + return amm_interface_plain.deploy_as_blueprint() + + +@pytest.fixture(scope="module") +def amm_interface_meta(): + return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") + + +@pytest.fixture(scope="module") +def amm_implementation_meta(deployer, amm_interface_meta): + with boa.env.prank(deployer): + return amm_interface_meta.deploy_as_blueprint() + + +@pytest.fixture(scope="module") +def stableswap_factory( + deployer, + fee_receiver, + owner, + amm_implementation_plain, + gauge_implementation, + weth, +): + with boa.env.prank(deployer): + factory = boa.load( + "contracts/main/CurveTricryptoFactory.vy", + fee_receiver, + owner, + weth, + ) + + with boa.env.prank(owner): + factory.set_plain_implementations(2, amm_implementation_plain) + factory.set_gauge_implementation(gauge_implementation) + + return factory + + +@pytest.fixture(scope="module") +def stableswap_factory_meta(factory, amm_implementation_meta): + # TODO: add Factory Meta Implementation + pass diff --git a/tests/fixtures/pool.py b/tests/fixtures/pool.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py new file mode 100644 index 00000000..5c939b66 --- /dev/null +++ b/tests/fixtures/pools.py @@ -0,0 +1,103 @@ +import boa +import pytest +from eth_utils import function_signature_to_4byte_selector + +# TODO: rebasing pool, pool with oracles, normal pool, pool with ETH + +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" + + +@pytest.fixture(scope="module") +def swap_plain( + stableswap_factory, + dai, + usdc, + bob, + amm_implementation_plain, +): + + with boa.env.prank(bob): + + pool = stableswap_factory.deploy_plain_pool( + "test", + "test", + [dai, usdc], + 2000, + 1000000, + 866, + [b""] * 4, + [ZERO_ADDRESS] * 4, + 0, + 0, + ) + + return amm_implementation_plain.at(pool) + + +@pytest.fixture(scope="module") +def swap_eth_rebasing( + stableswap_factory, + weth, + steth, + charlie, + amm_implementation_plain, +): + + # TODO: make steth rebasing + + with boa.env.prank(charlie): + + pool = stableswap_factory.deploy_plain_pool( + "test", + "test", + [weth, steth], + 1000, + 3000000, + 866, + [b""] * 4, + [ZERO_ADDRESS] * 4, + 0, + 0, + ) + + return amm_implementation_plain.at(pool) + + +@pytest.fixture(scope="module") +def swap_oracle( + stableswap_factory, + oracle_token_a, + oracle_token_b, + charlie, + amm_implementation_plain, +): + oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") + + with boa.env.prank(charlie): + + pool = stableswap_factory.deploy_plain_pool( + "test", + "test", + [oracle_token_a, oracle_token_b], + 500, + 4000000, + 866, + [oracle_method_id, oracle_method_id, b"", b""], + [ZERO_ADDRESS] * 4, + 0, + 0, + ) + + return amm_implementation_plain.at(pool) + + +@pytest.fixture(scope="module") +def swap_meta( + stableswap_factory, + dai, + base_pool_token, # TODO: implement base pool token + charlie, + amm_implementation_meta, +): + # TODO: implement metapools + pass diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index bbde3a68..341f8f52 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -11,19 +11,46 @@ def weth(deployer): @pytest.fixture(scope="module") def usdt(deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20Mock.vy", "USDT", "USDT", 6) + return boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", 6) @pytest.fixture(scope="module") def dai(deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20Mock.vy", "DAI", "DAI", 18) + return boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", 18) @pytest.fixture(scope="module") def steth(deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20Mock.vy", "stETH", "stETH", 18) + # TODO: turn this into a rebasing implementation + return boa.load( + "contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", 18 + ) + + +@pytest.fixture(scope="module") +def oracle_token_a(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTA", + "OTA", + 18, + 1006470359024000000, + ) + + +@pytest.fixture(scope="module") +def oracle_token_b(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTB", + "OTB", + 8, + 1007580460035000000, + ) @pytest.fixture(scope="module") From d1527342be2a41f13310c86300d29f2fcad4f7ba Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:33:10 +0200 Subject: [PATCH 006/337] wip getting test to run --- contracts/main/CurveStableSwapFactoryNG.vy | 16 ++++----- contracts/{mock => mocks}/CallbackSwap.vy | 0 contracts/{mock => mocks}/CallbackTestZap.vy | 0 contracts/{mock => mocks}/ERC20.vy | 0 contracts/{mock => mocks}/ERC20Oracle.vy | 0 contracts/{mock => mocks}/WETH.vy | 0 tests/fixtures/accounts.py | 4 +-- tests/fixtures/constants.py | 19 +++++++++++ tests/fixtures/factory.py | 20 ++++++----- tests/fixtures/pools.py | 20 +++++------ tests/fixtures/tokens.py | 2 +- tests/unitary/factory/test_factory.py | 0 tests/unitary/pool/plain/conftest.py | 11 +++++++ .../pool/plain/test_add_liquidity_initial.py | 33 +++++++++++++++++++ 14 files changed, 93 insertions(+), 32 deletions(-) rename contracts/{mock => mocks}/CallbackSwap.vy (100%) rename contracts/{mock => mocks}/CallbackTestZap.vy (100%) rename contracts/{mock => mocks}/ERC20.vy (100%) rename contracts/{mock => mocks}/ERC20Oracle.vy (100%) rename contracts/{mock => mocks}/WETH.vy (100%) create mode 100644 tests/fixtures/constants.py create mode 100644 tests/unitary/factory/test_factory.py create mode 100644 tests/unitary/pool/plain/conftest.py create mode 100644 tests/unitary/pool/plain/test_add_liquidity_initial.py diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index b9422a20..93a73aa7 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -791,6 +791,9 @@ def set_metapool_implementations( @param _base_pool Pool address to add @param _implementations Implementation address to use when deploying metapools """ + + # TODO: ensure only one implementation can be set at a time + assert msg.sender == self.admin # dev: admin-only function assert self.base_pool_data[_base_pool].coins[0] != empty(address) # dev: base pool does not exist @@ -807,18 +810,11 @@ def set_metapool_implementations( @external def set_plain_implementations( _n_coins: uint256, - _implementations: address[10], + _implementation_index: uint256, + _implementation: address, ): assert msg.sender == self.admin # dev: admin-only function - - for i in range(10): - new_imp: address = _implementations[i] - current_imp: address = self.plain_implementations[_n_coins][i] - if new_imp == current_imp: - if new_imp == empty(address): - break - else: - self.plain_implementations[_n_coins][i] = new_imp + self.plain_implementations[_n_coins][_implementation_index] = _implementation @external diff --git a/contracts/mock/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy similarity index 100% rename from contracts/mock/CallbackSwap.vy rename to contracts/mocks/CallbackSwap.vy diff --git a/contracts/mock/CallbackTestZap.vy b/contracts/mocks/CallbackTestZap.vy similarity index 100% rename from contracts/mock/CallbackTestZap.vy rename to contracts/mocks/CallbackTestZap.vy diff --git a/contracts/mock/ERC20.vy b/contracts/mocks/ERC20.vy similarity index 100% rename from contracts/mock/ERC20.vy rename to contracts/mocks/ERC20.vy diff --git a/contracts/mock/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy similarity index 100% rename from contracts/mock/ERC20Oracle.vy rename to contracts/mocks/ERC20Oracle.vy diff --git a/contracts/mock/WETH.vy b/contracts/mocks/WETH.vy similarity index 100% rename from contracts/mock/WETH.vy rename to contracts/mocks/WETH.vy diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index f947969c..642d25d5 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -16,8 +16,8 @@ def owner(): @pytest.fixture(scope="module") -def factory_admin(tricrypto_factory): - return tricrypto_factory.admin() +def factory_admin(factory): + return factory.admin() @pytest.fixture(scope="module") diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py new file mode 100644 index 00000000..85c39d71 --- /dev/null +++ b/tests/fixtures/constants.py @@ -0,0 +1,19 @@ +import pytest + + +@pytest.fixture(scope="session") +def initial_amounts(decimals): + return [1_000_000 * 10**precision for precision in decimals] + + +@pytest.fixture(scope="session") +def initial_amounts_underlying(underlying_decimals): + amts = [1_000_000 * 10**precision for precision in underlying_decimals] + for i in range(1, len(underlying_decimals)): + amts[i] //= 3 + return amts + + +@pytest.fixture(scope="session") +def deposit_amounts(decimals): + return [1_000 * 10**precision for precision in decimals] diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 1e89d9e0..e417619a 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -36,7 +36,7 @@ def amm_implementation_meta(deployer, amm_interface_meta): @pytest.fixture(scope="module") -def stableswap_factory( +def factory( deployer, fee_receiver, owner, @@ -45,21 +45,23 @@ def stableswap_factory( weth, ): with boa.env.prank(deployer): - factory = boa.load( - "contracts/main/CurveTricryptoFactory.vy", + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner, weth, ) with boa.env.prank(owner): - factory.set_plain_implementations(2, amm_implementation_plain) - factory.set_gauge_implementation(gauge_implementation) + _factory.set_plain_implementations(2, 0, amm_implementation_plain) + _factory.set_gauge_implementation(gauge_implementation) + # TODO: add Factory Meta Implementation - return factory + return _factory @pytest.fixture(scope="module") -def stableswap_factory_meta(factory, amm_implementation_meta): - # TODO: add Factory Meta Implementation - pass +def factory_populated( + factory, swap_plain, swap_eth_rebasing, swap_oracle, swap_meta +): + return factory diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 5c939b66..d7743bac 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,14 +2,14 @@ import pytest from eth_utils import function_signature_to_4byte_selector -# TODO: rebasing pool, pool with oracles, normal pool, pool with ETH +# TODO: rebasing pool, meta pool ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @pytest.fixture(scope="module") def swap_plain( - stableswap_factory, + factory, dai, usdc, bob, @@ -18,14 +18,14 @@ def swap_plain( with boa.env.prank(bob): - pool = stableswap_factory.deploy_plain_pool( + pool = factory.deploy_plain_pool( "test", "test", - [dai, usdc], + [dai, usdc, ZERO_ADDRESS, ZERO_ADDRESS], 2000, 1000000, 866, - [b""] * 4, + [bytes(b"")] * 4, [ZERO_ADDRESS] * 4, 0, 0, @@ -36,7 +36,7 @@ def swap_plain( @pytest.fixture(scope="module") def swap_eth_rebasing( - stableswap_factory, + factory, weth, steth, charlie, @@ -47,7 +47,7 @@ def swap_eth_rebasing( with boa.env.prank(charlie): - pool = stableswap_factory.deploy_plain_pool( + pool = factory.deploy_plain_pool( "test", "test", [weth, steth], @@ -65,7 +65,7 @@ def swap_eth_rebasing( @pytest.fixture(scope="module") def swap_oracle( - stableswap_factory, + factory, oracle_token_a, oracle_token_b, charlie, @@ -75,7 +75,7 @@ def swap_oracle( with boa.env.prank(charlie): - pool = stableswap_factory.deploy_plain_pool( + pool = factory.deploy_plain_pool( "test", "test", [oracle_token_a, oracle_token_b], @@ -93,7 +93,7 @@ def swap_oracle( @pytest.fixture(scope="module") def swap_meta( - stableswap_factory, + factory, dai, base_pool_token, # TODO: implement base pool token charlie, diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 341f8f52..27a693f0 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -9,7 +9,7 @@ def weth(deployer): @pytest.fixture(scope="module") -def usdt(deployer): +def usdc(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", 6) diff --git a/tests/unitary/factory/test_factory.py b/tests/unitary/factory/test_factory.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unitary/pool/plain/conftest.py b/tests/unitary/pool/plain/conftest.py new file mode 100644 index 00000000..c1939e00 --- /dev/null +++ b/tests/unitary/pool/plain/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture(scope="module") +def coins(swap_plain): + return [swap_plain.coins(0), swap_plain.coins(1)] + + +@pytest.fixture(scope="module") +def decimals(coins): + return [coins[0].decimals(), coins[1].decimals()] diff --git a/tests/unitary/pool/plain/test_add_liquidity_initial.py b/tests/unitary/pool/plain/test_add_liquidity_initial.py new file mode 100644 index 00000000..6615df2a --- /dev/null +++ b/tests/unitary/pool/plain/test_add_liquidity_initial.py @@ -0,0 +1,33 @@ +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + + +@pytest.mark.parametrize("min_amount", [0, 10**18]) +def test_initial( + alice, swap_plain, coins, decimals, min_amount, initial_amounts +): + + amounts = [10**d for d in decimals] + for idx in range(2): + mint_for_testing(coins[idx], alice, amounts[idx], mint_eth=False) + + with boa.env.prank(alice): + swap_plain.add_liquidity( + amounts, + min_amount, + ) + + for coin, amount, initial in zip(coins, amounts, initial_amounts): + + assert coin.balanceOf(alice) == initial - amount + assert coin.balanceOf(swap_plain) == amount + + +@pytest.mark.parametrize("idx", range(2)) +def test_initial_liquidity_missing_coin(alice, swap_plain, idx, decimals): + amounts = [10**i for i in decimals] + amounts[idx] = 0 + with boa.reverts(), boa.env.prank(alice): + swap_plain.add_liquidity(amounts, 0) From 4c866c5846cdc18ee9643b85654acbaedcdf00a3 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:29:12 +0200 Subject: [PATCH 007/337] wip: unified metapool implementation --- contracts/main/CurveStableSwap2NG.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 53 +++++++++++++++---------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index d795d2e7..514dfa11 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -236,7 +236,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) + self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: check if this line is correct self.ma_last_time = block.timestamp # EIP712 diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 5c15a4d0..bff92e61 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -140,9 +140,9 @@ admin_balances: public(uint256[N_COINS]) # ----------------------- Oracle Specific vars ------------------------------- -rate_multipliers: uint256[N_COINS] +rate_multiplier: uint256[N_COINS] # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: uint256[N_COINS] +oracle: uint256[N_COINS] last_prices_packed: uint256 # [last_price, ma_price] ma_exp_time: public(uint256) @@ -174,6 +174,13 @@ CACHED_CHAIN_ID: immutable(uint256) salt: public(immutable(bytes32)) CACHED_DOMAIN_SEPARATOR: immutable(bytes32) +# ----------------------- Base Pool Specific vars ---------------------------- + +BASE_POOL: immutable(address) +BASE_LP_TOKEN: immutable(address) +BASE_COINS: immutable(address[4]) +BASE_N_COINS: immutable(int128) + # ------------------------------ AMM Setup ----------------------------------- @@ -182,21 +189,24 @@ CACHED_DOMAIN_SEPARATOR: immutable(bytes32) def __init__( _name: String[32], _symbol: String[10], - _coins: address[4], - _rate_multipliers: uint256[4], + _coin: address, + _rate_multiplier: uint256, _A: uint256, _fee: uint256, _weth: address, _ma_exp_time: uint256, - _method_ids: bytes4[4], - _oracles: address[4], + _method_id: bytes4, + _oracle: address, + _base_pool: address, + _base_lp_token: address, + _base_pool_coins: address[4], ): """ @notice Initialize the pool contract @param _name Name of the new plain pool @param _symbol Symbol for the new plain pool - will be concatenated with factory symbol - @param _coins List of addresses of the coins being used in the pool. + @param _coin Addresses of the coin paired against the base pool's lp token. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. Suggested values include: @@ -208,26 +218,26 @@ def __init__( 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 - @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures - of the oracle addresses that gives rate oracles. - Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] - @param _oracles Array of rate oracle addresses. + @param _method_id First four bytes of the Keccak-256 hash of the function signature + of the oracle addresse that gives returns token rate. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Rate oracle addresse. """ WETH20 = _weth + BASE_POOL = _base_pool + BASE_LP_TOKEN = _base_lp_token - name = concat("Curve.fi Factory Plain Pool: ", _name) + name = concat("Curve.fi Factory Meta Pool: ", _name) symbol = concat(_symbol, "-f") - for i in range(N_COINS): - - coin: address = _coins[i] - if coin == empty(address): - break + self.coins = [_coin, BASE_LP_TOKEN] + self.rate_multiplier = _rate_multiplier + self.oracle = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) - self.coins[i] = coin - self.rate_multipliers[i] = _rate_multipliers[i] - self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + # TODO: initialise up base coins and base n coins here + for coin in BASE_COINS: + ERC20(coin).approve(BASE_POOL, MAX_UINT256) A: uint256 = _A * A_PRECISION self.initial_A = A @@ -237,10 +247,9 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) + self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: check if this line is correct self.ma_last_time = block.timestamp - # EIP712 NAME_HASH = keccak256(name) salt = block.prevhash CACHED_CHAIN_ID = chain.id From 24a4e860f0f706c5882344b9b595e61650ccd0f1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:40:13 +0200 Subject: [PATCH 008/337] wip --- contracts/main/CurveStableSwap2NG.vy | 117 ++++++++++++++++-------- contracts/main/CurveStableSwapMetaNG.vy | 8 +- 2 files changed, 84 insertions(+), 41 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 514dfa11..1d94c706 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -4,17 +4,22 @@ @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved @notice 2 coin pool implementation with no lending, i.e. tokens are not - deposited into lending markets + deposited into lending markets. Supports only token pairs that are + similarly priced (or the underlying is similarly priced). @dev ERC20 support for return True/revert, return True/False, return None ERC20 tokens can have arbitrary decimals (<=18). Additional features include: - 1. Support for positive-rebasing and fee-on-transfer tokens + 1. Support for rebasing tokens: but this disables + exchange_optimistically 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) 3. Support for ETH/WETH transfers 4. Adds oracles for coin[1] w.r.t coin[0] 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) + 6. Adds feature called exchange_with_rebase, which is inspired + by Uniswap V2: swaps that expect an ERC20 transfer to have occurred + prior to executing the swap. This is disabled for rebasing tokens. """ from vyper.interfaces import ERC20 @@ -108,6 +113,7 @@ coins: public(address[N_COINS]) N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 +IS_REBASING: immutable(bool) # ---------------------- Pool Amplification Parameters ----------------------- @@ -189,6 +195,7 @@ def __init__( _ma_exp_time: uint256, _method_ids: bytes4[4], _oracles: address[4], + _is_rebasing: bool ): """ @notice Initialize the pool contract @@ -211,9 +218,11 @@ def __init__( of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. + @param _is_rebasing """ WETH20 = _weth + IS_REBASING = _is_rebasing name = concat("Curve.fi Factory Plain Pool: ", _name) symbol = concat(_symbol, "-f") @@ -221,6 +230,7 @@ def __init__( for i in range(N_COINS): coin: address = _coins[i] + if coin == empty(address): break @@ -272,6 +282,7 @@ def set_ma_exp_time(_ma_exp_time: uint256): # ------------------ Token transfers in and out of the AMM ------------------- + @payable @external def __default__(): @@ -385,6 +396,57 @@ def _transfer_out( receiver, _amount, default_return_value=True ) +# -------------------------- AMM Special Methods ----------------------------- + + +@view +@internal +def _stored_rates() -> uint256[N_COINS]: + """ + @notice Gets rate multipliers for each coin. + @dev If the coin has a rate oracle that has been properly initialised, + this method queries that rate by static-calling an external + contract. + """ + + rates: uint256[N_COINS] = self.rate_multipliers + + for i in range(N_COINS): + oracle: uint256 = self.oracles[i] + if oracle == 0: + continue + + # NOTE: assumed that response is of precision 10**18 + response: Bytes[32] = raw_call( + convert(oracle % 2**160, address), + _abi_encode(oracle & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ) + + assert len(response) != 0 + rates[1] = rates[1] * convert(response, uint256) / PRECISION + + return rates + + +@view +@internal +def _balances() -> uint256[N_COINS]: + """ + @notice Calculates the pool's balances _excluding_ the admin's balances + @dev + """ + result: uint256[N_COINS] = empty(uint256[N_COINS]) + + if IS_REBASING: + for i in range(N_COINS): + result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + return result + else: + # TODO: add non rebasing support + return result + # -------------------------- AMM Main Functions ------------------------------ @@ -462,6 +524,23 @@ def exchange_extended( ) +@external +@nonreentrant('lock') +def exchange_optimistically( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + + if IS_REBASING: + raise + + return 0 + + @payable @external @nonreentrant('lock') @@ -793,40 +872,6 @@ def _exchange( return dy -@view -@internal -def _stored_rates() -> uint256[N_COINS]: - - rates: uint256[N_COINS] = self.rate_multipliers - - for i in range(N_COINS): - oracle: uint256 = self.oracles[i] - if oracle == 0: - continue - - # NOTE: assumed that response is of precision 10**18 - response: Bytes[32] = raw_call( - convert(oracle % 2**160, address), - _abi_encode(oracle & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ) - - assert len(response) != 0 - rates[1] = rates[1] * convert(response, uint256) / PRECISION - - return rates - - -@view -@internal -def _balances() -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - return result - - @view @internal def _A() -> uint256: diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index bff92e61..8b82528a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -142,7 +142,7 @@ admin_balances: public(uint256[N_COINS]) rate_multiplier: uint256[N_COINS] # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracle: uint256[N_COINS] +oracles: uint256 last_prices_packed: uint256 # [last_price, ma_price] ma_exp_time: public(uint256) @@ -233,7 +233,7 @@ def __init__( self.coins = [_coin, BASE_LP_TOKEN] self.rate_multiplier = _rate_multiplier - self.oracle = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) + self.oracles = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) # TODO: initialise up base coins and base n coins here for coin in BASE_COINS: @@ -1542,9 +1542,7 @@ def get_balances() -> uint256[N_COINS]: @view @external def oracle(_idx: uint256) -> address: - if _idx < N_COINS: - return convert(self.oracles[_idx] % 2**160, address) - return empty(address) + return convert(self.oracle % 2**160, address) # --------------------------- AMM Admin Functions ---------------------------- From acbfb67fc840e35c0ad25b70558ad34bebf9b0b8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:52:26 +0200 Subject: [PATCH 009/337] feat: add to account for pool's token balances on every liquidity movement in and out of the pool --- contracts/main/CurveStableSwap2NG.vy | 138 ++++++++++++++++++++------- 1 file changed, 102 insertions(+), 36 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 1d94c706..f0404ee0 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -103,18 +103,19 @@ event ApplyNewFee: fee: uint256 -# ---------------------------- Pool Parameters ------------------------------- +# ---------------------------- Pool Variables -------------------------------- WETH20: public(immutable(address)) -factory: public(address) - -coins: public(address[N_COINS]) N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 IS_REBASING: immutable(bool) +factory: public(address) +coins: public(address[N_COINS]) +stored_balances: uint256[N_COINS] + # ---------------------- Pool Amplification Parameters ----------------------- A_PRECISION: constant(uint256) = 100 @@ -434,18 +435,34 @@ def _stored_rates() -> uint256[N_COINS]: @internal def _balances() -> uint256[N_COINS]: """ - @notice Calculates the pool's balances _excluding_ the admin's balances - @dev + @notice Calculates the pool's balances _excluding_ the admin's balances. + @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: uint256[N_COINS] = empty(uint256[N_COINS]) - if IS_REBASING: + if not IS_REBASING: + return self.stored_balances # only used for optimistic swaps + else: for i in range(N_COINS): result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - return result - else: - # TODO: add non rebasing support - return result + + return result + + +@internal +def _increase_balances(balances: uint256[N_COINS]): + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] += balances[i] + self.stored_balances = stored_balances + + +@internal +def _decrease_balances(balances: uint256[N_COINS]): + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] -= balances[i] + self.stored_balances = stored_balances # -------------------------- AMM Main Functions ------------------------------ @@ -463,6 +480,7 @@ def exchange( _receiver: address = msg.sender, ) -> uint256: """ + # TODO: Add docs @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method Allows for native token swaps (e.g. ETH <> whatever) @@ -483,7 +501,8 @@ def exchange( _use_eth, _receiver, empty(address), - empty(bytes32) + empty(bytes32), + False ) @@ -500,6 +519,7 @@ def exchange_extended( _cb: bytes32 ) -> uint256: """ + # TODO: Add docs @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method Not payable (does not accept eth) @@ -520,7 +540,8 @@ def exchange_extended( _use_eth, _receiver, msg.sender, # <---------------------------- callbacker is msg.sender. - _cb + _cb, + False ) @@ -534,11 +555,24 @@ def exchange_optimistically( _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: - - if IS_REBASING: - raise - - return 0 + """ + @notice Exchange + # TODO: Add docs + """ + assert not IS_REBASING, "Cannot swap optimistically if pool contains rebasing token(s)" + return self._exchange( + msg.sender, + 0, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32), + True, # <--------------------------------------- swap optimistically. + ) @payable @@ -604,6 +638,9 @@ def add_liquidity( assert total_supply != 0 # dev: initial deposit requires all coins + # Add incoming balance + self._increase_balances(new_balances) + # Invariant after change D1: uint256 = self.get_D_mem(rates, new_balances, amp) assert D1 > D0 @@ -680,6 +717,9 @@ def remove_liquidity_one_coin( self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + # Decrease coin[i] balance in self.stored_balances + self.stored_balances[i] -= dy[0] + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) self.save_p_from_price(dy[2]) @@ -710,11 +750,12 @@ def remove_liquidity_imbalance( new_balances: uint256[N_COINS] = old_balances for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - new_balances[i] -= amount + if _amounts[i] != 0: + new_balances[i] -= _amounts[i] + self._transfer_out(coins[i], _amounts[i], _use_eth, _receiver) - self._transfer_out(coins[i], amount, _use_eth, _receiver) + # Decrease balances in self.stored_balances + self._decrease_balances(_amounts) D1: uint256 = self.get_D_mem(rates, new_balances, amp) @@ -776,9 +817,11 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(coins[i], value, _use_eth, _receiver) + # Decrease balances in self.stored_balances + self._decrease_balances(amounts) + total_supply -= _burn_amount self.balanceOf[msg.sender] -= _burn_amount self.totalSupply = total_supply @@ -815,7 +858,8 @@ def _exchange( use_eth: bool, receiver: address, callbacker: address, - callback_sig: bytes32 + callback_sig: bytes32, + optimistic_swap: bool = False ) -> uint256: assert i != j # dev: coin index out of range @@ -828,13 +872,27 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- - # `dx` that `_transfer_in` gives is whatever the pool received after ERC20 - # transfer - dx: uint256 = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth - ) + dx: uint256 = 0 + + if optimistic_swap: + + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] + + assert dx == _dx, "Pool did not receive tokens for swap" + + else: + + # `dx` is whatever the pool received after ERC20 transfer: + dx = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) + + # Update stored balances + self.stored_balances[i] += dx # ------------------------------------------------------------------------ @@ -865,6 +923,9 @@ def _exchange( self._transfer_out(coins[j], dy, use_eth, receiver) + # Update Stored Balances: + self.stored_balances[j] -= dy + # ------------------------------------------------------------------------ log TokenExchange(msg.sender, i, _dx, j, dy) @@ -1095,23 +1156,26 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: @internal def _withdraw_admin_fees(): + receiver: address = Factory(self.factory).get_fee_receiver(self) + amounts: uint256[N_COINS] = self.admin_balances for i in range(N_COINS): - amount: uint256 = self.admin_balances[i] - if amount > 0: + if amounts[i] > 0: if self.coins[i] == WETH20: - raw_call(receiver, b"", value=amount) + raw_call(receiver, b"", value=amounts[i]) else: assert ERC20(self.coins[i]).transfer( receiver, - amount, + amounts[i], default_return_value=True ) self.admin_balances = empty(uint256[N_COINS]) + # Reduce stored balances: + self._decrease_balances(amounts) # -------------------------- AMM Price Methods ------------------------------- @@ -1401,7 +1465,9 @@ def ema_price() -> uint256: @view def get_p() -> uint256: amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + xp: uint256[N_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) D: uint256 = self.get_D(xp, amp) return self._get_p(xp, amp, D) From 5ec8397eb34ab9b69588cf0da21a1e08ac0eba2a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 24 Jun 2023 14:19:08 +0200 Subject: [PATCH 010/337] fix: bug in oracle --- contracts/main/CurveStableSwap2NG.vy | 29 ++++++++++++++-------- contracts/main/CurveStableSwapFactoryNG.vy | 22 ++++++---------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index f0404ee0..747469b5 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -3,23 +3,29 @@ @title CurveStableSwap2NG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice 2 coin pool implementation with no lending, i.e. tokens are not - deposited into lending markets. Supports only token pairs that are +@notice 2 coin pool implementation with no rehypothecation, i.e. tokens are not + deposited into contracts. Supports only token pairs that are similarly priced (or the underlying is similarly priced). @dev ERC20 support for return True/revert, return True/False, return None ERC20 tokens can have arbitrary decimals (<=18). Additional features include: 1. Support for rebasing tokens: but this disables - exchange_optimistically + `exchange_optimistically` 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + Note: Oracle precision _must_ be 10**18 3. Support for ETH/WETH transfers 4. Adds oracles for coin[1] w.r.t coin[0] 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature called exchange_with_rebase, which is inspired + 6. Adds feature called `exchange_optimistically`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred - prior to executing the swap. This is disabled for rebasing tokens. + prior to executing the swap. + Note: a. If pool contains rebasing tokens and `IS_REBASING` is True + then calling `exchange_optimistically` will REVERT. + b. If pool contains rebasing token and `IS_REBASING` is False + then this is an incorrect implementation and rebases can be + stolen. """ from vyper.interfaces import ERC20 @@ -219,7 +225,7 @@ def __init__( of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _is_rebasing + @param _is_rebasing If any of the coins rebases, then this should be set to True. """ WETH20 = _weth @@ -411,22 +417,23 @@ def _stored_rates() -> uint256[N_COINS]: """ rates: uint256[N_COINS] = self.rate_multipliers + oracles: uint256[N_COINS] = self.oracles for i in range(N_COINS): - oracle: uint256 = self.oracles[i] - if oracle == 0: + + if oracles[i] == 0: continue # NOTE: assumed that response is of precision 10**18 response: Bytes[32] = raw_call( - convert(oracle % 2**160, address), - _abi_encode(oracle & ORACLE_BIT_MASK), + convert(oracles[i] % 2**160, address), + _abi_encode(oracles[i] & ORACLE_BIT_MASK), max_outsize=32, is_static_call=True, ) assert len(response) != 0 - rates[1] = rates[1] * convert(response, uint256) / PRECISION + rates[i] = rates[i] * convert(response, uint256) / PRECISION return rates diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 93a73aa7..d2ffa07a 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -41,20 +41,6 @@ interface ERC20: def totalSupply() -> uint256: view def approve(_spender: address, _amount: uint256): nonpayable -interface CurvePlainPool: - def initialize( - _name: String[32], - _symbol: String[10], - _coins: address[4], - _rate_multipliers: uint256[4], - _A: uint256, - _fee: uint256, - _weth: address, - _ma_exp_time: uint256, - _method_ids: bytes4[4], - _oracles: address[4] - ): nonpayable - interface CurvePool: def A() -> uint256: view def fee() -> uint256: view @@ -521,6 +507,7 @@ def deploy_plain_pool( _oracles: address[4] = empty(address[4]), _asset_type: uint256 = 0, _implementation_idx: uint256 = 0, + _is_rebasing: bool = False ) -> address: """ @notice Deploy a new plain pool @@ -548,6 +535,7 @@ def deploy_plain_pool( @param _implementation_idx Index of the implementation to use. All possible implementations for a pool of N_COINS can be publicly accessed via `plain_implementations(N_COINS)` + @param _is_rebasing If any of the coins rebases, then this should be set to True. @return Address of the deployed pool """ assert _fee <= 100000000, "Invalid fee" @@ -591,6 +579,7 @@ def deploy_plain_pool( _ma_exp_time, _method_ids, _oracles, + _is_rebasing, code_offset=3 ) @@ -637,7 +626,11 @@ def deploy_metapool( _coin: address, _A: uint256, _fee: uint256, + _ma_exp_time: uint256, + _method_ids: bytes4[4] = empty(bytes4[4]), + _oracles: address[4] = empty(address[4]), _implementation_idx: uint256 = 0, + _is_rebasing: bool = False ) -> address: """ @notice Deploy a new metapool @@ -659,6 +652,7 @@ def deploy_metapool( @param _implementation_idx Index of the implementation to use. All possible implementations for a BASE_POOL can be publicly accessed via `metapool_implementations(BASE_POOL)` + @param _is_rebasing If coin rebases, then this should be set to True. @return Address of the deployed pool """ assert _fee <= 100000000, "Invalid fee" From 850b39cc051b179a01e846622a3fbe492730ef63 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 24 Jun 2023 14:24:13 +0200 Subject: [PATCH 011/337] feat: enforce ETH at 0 index --- contracts/main/CurveStableSwap2NG.vy | 32 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 747469b5..6956819b 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -12,9 +12,9 @@ 1. Support for rebasing tokens: but this disables `exchange_optimistically` 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) - Note: Oracle precision _must_ be 10**18 + Note: Oracle precision _must_ be 10**18. 3. Support for ETH/WETH transfers - 4. Adds oracles for coin[1] w.r.t coin[0] + 4. Adds oracles based on AMM State Price (and _not_ last traded price). 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) @@ -241,6 +241,10 @@ def __init__( if coin == empty(address): break + # Enforce native token as coin[0] (makes integrators happy) + if coin == WETH20 and i > 0: + raise "ETH must be at index 0" + self.coins[i] = coin self.rate_multipliers[i] = _rate_multipliers[i] self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) @@ -275,18 +279,6 @@ def __init__( log Transfer(empty(address), self, 0) -@external -def set_ma_exp_time(_ma_exp_time: uint256): - """ - @notice Set the moving average window of the price oracle. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) - """ - assert msg.sender == Factory(self.factory).admin() # dev: only owner - assert _ma_exp_time != 0 - - self.ma_exp_time = _ma_exp_time - - # ------------------ Token transfers in and out of the AMM ------------------- @@ -1716,3 +1708,15 @@ def apply_new_fee(): self.fee = fee self.admin_action_deadline = 0 log ApplyNewFee(fee) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert _ma_exp_time != 0 + + self.ma_exp_time = _ma_exp_time From 74ec5c0a589aae2dbf0dcb51c77c96f803e0cf41 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 24 Jun 2023 14:31:51 +0200 Subject: [PATCH 012/337] fix: change name to exchange_with_rebase --- contracts/main/CurveStableSwap2NG.vy | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 6956819b..e32d0e82 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -10,7 +10,7 @@ ERC20 tokens can have arbitrary decimals (<=18). Additional features include: 1. Support for rebasing tokens: but this disables - `exchange_optimistically` + `exchange_with_rebase` 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. 3. Support for ETH/WETH transfers @@ -18,11 +18,11 @@ 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature called `exchange_optimistically`, which is inspired + 6. Adds feature called `exchange_with_rebase`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and `IS_REBASING` is True - then calling `exchange_optimistically` will REVERT. + then calling `exchange_with_rebase` will REVERT. b. If pool contains rebasing token and `IS_REBASING` is False then this is an incorrect implementation and rebases can be stolen. @@ -479,7 +479,6 @@ def exchange( _receiver: address = msg.sender, ) -> uint256: """ - # TODO: Add docs @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method Allows for native token swaps (e.g. ETH <> whatever) @@ -518,10 +517,10 @@ def exchange_extended( _cb: bytes32 ) -> uint256: """ - # TODO: Add docs - @notice Perform an exchange between two coins + @notice Perform an exchange between two coins after a callback @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth) + Not payable (does not accept eth). Users of this method are dex aggregators, + arbitrageurs, or other users who do not wish to grant approvals to the contract. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -546,7 +545,7 @@ def exchange_extended( @external @nonreentrant('lock') -def exchange_optimistically( +def exchange_with_rebase( i: int128, j: int128, _dx: uint256, @@ -555,8 +554,16 @@ def exchange_optimistically( _receiver: address = msg.sender, ) -> uint256: """ - @notice Exchange - # TODO: Add docs + @notice Perform an exchange between two coins without transferring token in + @dev The contract swaps tokens based on a change in balance of coin[i]. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract. The method is non-payable. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received """ assert not IS_REBASING, "Cannot swap optimistically if pool contains rebasing token(s)" return self._exchange( From b6b90bea132be8d57cf74b54205b7825ac66e0b5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:55:18 +0200 Subject: [PATCH 013/337] fix: remove IS_REBASING logic from _balances --- contracts/main/CurveStableSwap2NG.vy | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index e32d0e82..6f46dc65 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -438,12 +438,8 @@ def _balances() -> uint256[N_COINS]: @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: uint256[N_COINS] = empty(uint256[N_COINS]) - - if not IS_REBASING: - return self.stored_balances # only used for optimistic swaps - else: - for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + for i in range(N_COINS): + result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] return result From 0cc1aa3ed67aae7b97840534d762a89fb7b3211f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:44:55 +0200 Subject: [PATCH 014/337] feat: add liquidity with rebase --- contracts/main/CurveStableSwap2NG.vy | 440 ++++++++++++++++----------- 1 file changed, 260 insertions(+), 180 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 6f46dc65..6c6def7f 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -554,14 +554,16 @@ def exchange_with_rebase( @dev The contract swaps tokens based on a change in balance of coin[i]. The dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract. The method is non-payable. + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `exchange_on_rebase`. + The method is non-payable: does not accept native token. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not IS_REBASING, "Cannot swap optimistically if pool contains rebasing token(s)" + assert not IS_REBASING, "Call disabled if IS_REBASING is True" return self._exchange( msg.sender, 0, @@ -593,101 +595,42 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - coins: address[N_COINS] = self.coins - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - - if _amounts[i] > 0: - - if coins[i] == WETH20: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - msg.value, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - _use_eth - ) - - else: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - False - ) - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - # Add incoming balance - self._increase_balances(new_balances) - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - - if total_supply > 0: - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) - - else: - - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[_receiver] += mint_amount - self.totalSupply = total_supply - log Transfer(empty(address), _receiver, mint_amount) + return self._add_liquidity( + msg.sender, + _amounts, + _min_mint_amount, + _use_eth, + _receiver, + msg.value, + False, # <--------------------- Does not expect optimistic transfers. + ) - log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) - return mint_amount +@external +@nonreentrant('lock') +def add_liquidity_with_rebase( + _amounts: uint256[N_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._add_liquidity( + msg.sender, + _amounts, + _min_mint_amount, + _use_eth, + _receiver, + 0, + True, # <------------------------------ Expects optimistic transfers. + ) @external @@ -861,7 +804,7 @@ def _exchange( receiver: address, callbacker: address, callback_sig: bytes32, - optimistic_swap: bool = False + expect_optimistic_transfer: bool = False ) -> uint256: assert i != j # dev: coin index out of range @@ -876,7 +819,7 @@ def _exchange( dx: uint256 = 0 - if optimistic_swap: + if expect_optimistic_transfer: # This branch is never reached for rebasing tokens pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) @@ -935,78 +878,165 @@ def _exchange( return dy -@view @internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A +def _add_liquidity( + sender: address, + amounts: uint256[N_COINS], + min_mint_amount: uint256, + use_eth: bool, + receiver: address, + mvalue: uint256, + expect_optimistic_transfer: bool = False, +) -> uint256: - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + coins: address[N_COINS] = self.coins - else: # when t1 == 0 or block.timestamp >= t1 - return A1 + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances -@pure -@internal -def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = _rates[i] * _balances[i] / PRECISION - return result + # -------------------------- Do Transfers In ----------------------------- + if expect_optimistic_transfer: -@pure -@internal -def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively + dx: uint256 = 0 - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + for i in range(N_COINS): - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - for x in _xp: - S += x - if S == 0: - return 0 + if amounts[i] > 0: - D: uint256 = S - Ann: uint256 = _amp * N_COINS - for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS - Dprev: uint256 = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) - # Equality with the precision of 1 - if D > Dprev: - if D - Dprev <= 1: - return D - else: - if Dprev - D <= 1: - return D - # convergence typically occurs in 4 rounds or less, this should be unreachable! - # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` - raise + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] + + assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" + + new_balances[i] += dx + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + else: + + for i in range(N_COINS): + + if amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + mvalue, + empty(address), + empty(bytes32), + sender, + empty(address), + use_eth + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + sender, + empty(address), + False + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Add incoming balance + self._increase_balances(new_balances) + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), receiver, mint_amount) + + log AddLiquidity(sender, amounts, fees, D1, total_supply) + + return mint_amount -@view @internal -def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) - return self.get_D(xp, _amp) +def _withdraw_admin_fees(): + + receiver: address = Factory(self.factory).get_fee_receiver(self) + amounts: uint256[N_COINS] = self.admin_balances + + for i in range(N_COINS): + + if amounts[i] > 0: + + if self.coins[i] == WETH20: + raw_call(receiver, b"", value=amounts[i]) + else: + assert ERC20(self.coins[i]).transfer( + receiver, + amounts[i], + default_return_value=True + ) + + self.admin_balances = empty(uint256[N_COINS]) + # Reduce stored balances: + self._decrease_balances(amounts) + + +# --------------------------- AMM Math Functions ----------------------------- @view @@ -1069,6 +1099,42 @@ def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, raise +@pure +@internal +def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for i in range(255): + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + Dprev: uint256 = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + @pure @internal def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: @@ -1117,6 +1183,44 @@ def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: raise +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@pure +@internal +def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = _rates[i] * _balances[i] / PRECISION + return result + + +@view +@internal +def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: + xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + @view @internal def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: @@ -1156,30 +1260,6 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: return [dy, dy_0 - dy, last_p] -@internal -def _withdraw_admin_fees(): - - receiver: address = Factory(self.factory).get_fee_receiver(self) - amounts: uint256[N_COINS] = self.admin_balances - - for i in range(N_COINS): - - if amounts[i] > 0: - - if self.coins[i] == WETH20: - raw_call(receiver, b"", value=amounts[i]) - else: - assert ERC20(self.coins[i]).transfer( - receiver, - amounts[i], - default_return_value=True - ) - - self.admin_balances = empty(uint256[N_COINS]) - # Reduce stored balances: - self._decrease_balances(amounts) - - # -------------------------- AMM Price Methods ------------------------------- From eb46a73393c31b6e1b078fefcb1e90528caf90a5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:57:44 +0200 Subject: [PATCH 015/337] add docstring --- contracts/main/CurveStableSwap2NG.vy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 6c6def7f..f8cfcf78 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -616,6 +616,12 @@ def add_liquidity_with_rebase( ) -> uint256: """ @notice Deposit coins into the pool + @dev The contract adds liquidity based on a change in balance of coins. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `add_liquidity_on_rebase`. + The method is non-payable: does not accept native token. @param _amounts List of amounts of coins to deposit @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit @param _receiver Address that owns the minted LP tokens From 6025405bff6f9a4a66189827b5e641111d308412 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:01:37 +0200 Subject: [PATCH 016/337] more docstrings --- contracts/main/CurveStableSwap2NG.vy | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index f8cfcf78..f1fd4796 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -4,13 +4,13 @@ @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved @notice 2 coin pool implementation with no rehypothecation, i.e. tokens are not - deposited into contracts. Supports only token pairs that are + deposited into other contracts. Supports only token pairs that are similarly priced (or the underlying is similarly priced). @dev ERC20 support for return True/revert, return True/False, return None ERC20 tokens can have arbitrary decimals (<=18). Additional features include: 1. Support for rebasing tokens: but this disables - `exchange_with_rebase` + `exchange_with_rebase` and `add_liquidity_with_rebase` 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. 3. Support for ETH/WETH transfers @@ -18,7 +18,7 @@ 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature called `exchange_with_rebase`, which is inspired + 6. Adds feature: `exchange_with_rebase`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and `IS_REBASING` is True @@ -26,6 +26,12 @@ b. If pool contains rebasing token and `IS_REBASING` is False then this is an incorrect implementation and rebases can be stolen. + 7. Adds feature: `add_liquidity_with_rebase`. This is a version of + `add_liquidity` with optimistic ERC20 token transfers. As with + `exchange_with_rebase`, `IS_REBASING = True` disables this method. + 8. Adds `get_dx`: Similar to `get_dy` which returns an expected output + of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected + input of coin[i] for an output amount of coin[j]. """ from vyper.interfaces import ERC20 From 280af3fa785cd21e1c1ebf632678d6ec9afe480a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:00:25 +0200 Subject: [PATCH 017/337] remove concatenation with Curve.fi blabla --- contracts/main/CurveStableSwap2NG.vy | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index f1fd4796..43a509f6 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -212,9 +212,8 @@ def __init__( ): """ @notice Initialize the pool contract - @param _name Name of the new plain pool - @param _symbol Symbol for the new plain pool - will be - concatenated with factory symbol + @param _name Name of the new plain pool. + @param _symbol Symbol for the new plain pool. @param _coins List of addresses of the coins being used in the pool. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. @@ -237,8 +236,8 @@ def __init__( WETH20 = _weth IS_REBASING = _is_rebasing - name = concat("Curve.fi Factory Plain Pool: ", _name) - symbol = concat(_symbol, "-f") + name = _name + symbol = _symbol for i in range(N_COINS): @@ -401,6 +400,7 @@ def _transfer_out( receiver, _amount, default_return_value=True ) + # -------------------------- AMM Special Methods ----------------------------- @@ -452,6 +452,11 @@ def _balances() -> uint256[N_COINS]: @internal def _increase_balances(balances: uint256[N_COINS]): + """ + @notice Increases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer into the pool. + """ stored_balances: uint256[N_COINS] = self.stored_balances for i in range(N_COINS): stored_balances[i] += balances[i] @@ -460,6 +465,11 @@ def _increase_balances(balances: uint256[N_COINS]): @internal def _decrease_balances(balances: uint256[N_COINS]): + """ + @notice Decreases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer out of the pool. + """ stored_balances: uint256[N_COINS] = self.stored_balances for i in range(N_COINS): stored_balances[i] -= balances[i] From 25ec6b3dfa6a54b78be77db88c0d7c405011121c Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 26 Jun 2023 20:55:53 +0200 Subject: [PATCH 018/337] add poetry environment --- .github/workflows/unit-tests.yaml | 2 +- .gitignore | 1 + .pre-commit-config.yaml | 4 +- .python-version | 1 - README.MD | 14 + poetry.lock | 2895 +++++++++++++++++++++++++++++ pyproject.toml | 60 + requirements.txt | 23 - 8 files changed, 2973 insertions(+), 27 deletions(-) delete mode 100644 .python-version create mode 100644 README.MD create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 9bcecfc5..09c526b1 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -24,7 +24,7 @@ jobs: python-version: 3.10.4 - name: Install Requirements - run: pip install -r requirements.txt + run: pip install poetry==1.5.1 & poetry install - name: Run Tests run: python -m pytest tests/boa -n auto diff --git a/.gitignore b/.gitignore index 107b9cc3..7c26134f 100644 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,4 @@ node_modules # temp docs/ scripts/experiments/get_p.py +.python-version diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11b0062b..f585a7de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: rev: 22.3.0 hooks: - id: black - args: [--line-length=79] + args: [--line-length=120] - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: @@ -20,7 +20,7 @@ repos: rev: 5.12.0 hooks: - id: isort - args: ["--profile", "black", --line-length=79] + args: ["--profile", "black", --line-length=120] default_language_version: python: python3.10.4 diff --git a/.python-version b/.python-version deleted file mode 100644 index 8d7f852b..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.10.4 diff --git a/README.MD b/README.MD new file mode 100644 index 00000000..5a21422c --- /dev/null +++ b/README.MD @@ -0,0 +1,14 @@ +# Stableswap NG + +## Testing + +Install dependencies using poetry (python ^3.10.4) + +```shell +pip install poetry==1.5.1 +poetry install +``` + +```shell +python -m pytest tests/boa -n auto +``` diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..cbe842a3 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2895 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "bitarray" +version = "2.7.6" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-2.7.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:82816e361303817ac79d6870d51a3c7f1f343e816a0b1d885b713389d9bba425"}, + {file = "bitarray-2.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b798d37fcd8e78e381660a65c434c3f60713c6f076fcecfd22544ec46d7416e6"}, + {file = "bitarray-2.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b133a161bb4426e0bdd0e53be979dfdc5f63f14043b8e194d1fba6391e1672"}, + {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aafd10decf8940e8f2d21e25b32b21ae9fd1aa58f168ea97f0f82adb0f6aa33a"}, + {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:913e767a3d9a50d2213f5e22db4e87b8a29acc38be5001d29b6be42b1361b812"}, + {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ffcde9d694bfb34f82e6739b33fd267bad25e6b4042b43bfffbe3ba31184318"}, + {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be9ffbb6dd777233331b2fc08d30957cb01f86e2ee11aa5d7652158fa3b795"}, + {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:190d7e018565f0745c76ced5060c2a0a218efb5b3e5df71f8a0da5661decaaf7"}, + {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab8fc4de49c75ef358fa9a30a367a163f62b92048a3da084500b88283aba47a6"}, + {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1211d16de1814f910fc7f2de74930ea22b4feb9b699eed5ceaef8ed14736fe50"}, + {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0f8e85acce0c3434776fc82819dfd09d9003595456f838d50b1009d6ac0be031"}, + {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e095f92ba7c6424fc4918f28ddc998e1628bbf83bd5b0405c08c5591f74560a3"}, + {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98959be5e864bed9c4fcd90d47d96cf5250d86a2a22b39ec41514c54c80f29bd"}, + {file = "bitarray-2.7.6-cp310-cp310-win32.whl", hash = "sha256:c6a13db2090c16a8753a6b6fb0e5ac0c7ece1930d4fa85c20e885aec550bceb3"}, + {file = "bitarray-2.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:31b2855bd566c965ec00ca5629a2637786e0605005826be0f6f192f3756f2aa2"}, + {file = "bitarray-2.7.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fd489d5b82679528fe096084c4e6976199874edc3c126bb41cd668987bd784"}, + {file = "bitarray-2.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:557cc0858e5171b542ed48bbb58ae83bc65da82fb3fd88dafd21e33ba1b685d3"}, + {file = "bitarray-2.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e40be8a46062ff89c07fe00a0ea7e06a069805cf1f9cb57196a89f52fdcaf80"}, + {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e1abda299b516573aa916e4c58bb870dd55f17dd544041d9290cb1c005be30"}, + {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6897bfadf3077685c73cd6ca60ebeaaa40b3bbe060f38f0a6c577bebe37935f4"}, + {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a9347acb24a0ee50cc932c52f58337d5b9503bb5b78f435cf3a47cd7031d86"}, + {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f16a5e4fd0614d43c1bd5bd0887deca1fcf292253662bce040c169492005fd8a"}, + {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a871ee6f460d03febdd1577ddab66c7f297a43eff3293ee4cb34f6eb5cacb6"}, + {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e373bd92a6caf9bb6bb2918e3f6e64ba10045d62c2e376d80d0ec062dcdb19ef"}, + {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ca4dc98b56461d3656f3219cfcc4a6845479ab7e6cdc134c7fa2615336c55f76"}, + {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:2b3527f41bbfd4b8b64b9d7541cd9f8234b0d5a477e59b8c7f666f6f9635ea09"}, + {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d77b8ed242fa4b70a609e0bf81dd1f2db161ff4a082f7dfe233b491fe4c0c39"}, + {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:efe64997aeabedbaf24543020cc5c672d00cb373e126c12fe7e21e128f548001"}, + {file = "bitarray-2.7.6-cp311-cp311-win32.whl", hash = "sha256:5ac235eb59737bffce44d605a2c1af47e56e28a98bc7c9c763587e8bd8c5ebe0"}, + {file = "bitarray-2.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:2c2bbb73b8dbc52dd26ae40ae83f2e863fc48db6c37ea7fbfd5a750ec62c0a30"}, + {file = "bitarray-2.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d15685830b607088f040ec512d045ab762678d33e813e882a260ae3ee3d381db"}, + {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94333a9f419ddf2f92ddbdb34a10a4b2c13e346f077511a35de87a0acea4289f"}, + {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cff6588ec89b276bab617b15778b20aeb5124df1e04b1c0b5364e9cb8dce6a7f"}, + {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60acdb34def45f72f50a2277763b2a979e4c42dcb4c183a3a9a75bfe195e0b65"}, + {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88bfe996b1adb811690333523e2500d700f6d3dfb282b0b77194e8ff1d168873"}, + {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56e6e68d0a508b0a2bd81840e31c41f4858585cd72f192264c4f0f8f48fd0c1"}, + {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e1ec302eda30e87793bd31c16c18a92118a1072831200247d0a8430e095e9563"}, + {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d054a65a9b3848744e49f195ef7399841c545c05ad11854982e271331a4577c"}, + {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:26e4f99f5bdb20c9275dcb5bde44aaae74c52224f328bf1f691be43d6eaccc2b"}, + {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:523de6f222c310a865f4070f79b2ad0e0b35b9f26b34ee9f93a3dc8ad293dbfb"}, + {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bcc8bd9fe25b48cff1702a6ef700f7a7726c8084be8edcae67a1df0d23caa822"}, + {file = "bitarray-2.7.6-cp36-cp36m-win32.whl", hash = "sha256:0efcd03263f70f76298e56b6c5ed454ba89c01777ac25a790725bc063c6170b9"}, + {file = "bitarray-2.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:31c118dcaaad605c3d8ef953c79554f4822e454e85649dd9fe82e3fcd5017eef"}, + {file = "bitarray-2.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:216c113c68523b0d3a0f98c5d12236a1998b5c3bf79bfa17324368bf9d20cd26"}, + {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4451de47a3b2b378f03bc12d4cf49e64895dc263befee2af38420a91e38d4f9"}, + {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ad4c0e7e20c7384a111ce1883b91e203fc7950a6d4bb3ccdad23422b359ab4a"}, + {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f57e3e6362d2ca7ab797e423a2d5ed1934b8599d197097458c714cf107f4dc4a"}, + {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f21b59a436ec2f8226c397292e24f9dfb9c3eba904038f50778f2365e68fdea"}, + {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae83573d9f30f085755c7ba519f0498ecb773a0f2a7e428ed2a33324975e1cec"}, + {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f36666371fcf8c1adaa0184b5762a7ce8310fdd33f5c39bf085c6dfe5fb0699"}, + {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5df77a37750fff8a107fba2e7adb887d171ae923306fa9a6c6c6c1a22c73da7f"}, + {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:08abec79d033ee10d6f4cb2fa9aec952d18eb06ce034b9cdf0cd8e6f2d023254"}, + {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:790aa2272aa6a2b792404a9f672dd136801a3a3860f74298c2542f63a206ed49"}, + {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95749278b59247a2a5779f1a0fc216f110eea1aa8af330609b8bdb5f8f55b78f"}, + {file = "bitarray-2.7.6-cp37-cp37m-win32.whl", hash = "sha256:5ebb24415e5838a729484ae698e1a17e27b826d2691d5cc6d4dc44a90f6a89e6"}, + {file = "bitarray-2.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:79da57026eb2c6b266c55d578b607f71d2eb9c5ae1bb3b32232d7dae059d63ed"}, + {file = "bitarray-2.7.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e9065cbc14ac490e4b1b5520ef33d61e9bdb41d066c7f18b20429c3025ea07e"}, + {file = "bitarray-2.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e6f228712a5696f3e13048894fc1f6d70d32421e154476b74595b0309a9d9dcf"}, + {file = "bitarray-2.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b803225158bbe17384e0a9f15cffecb5ae019b4d225a21887670ef20acfa4c8"}, + {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc2fd90298cb5e4c5882a8d8f4b39db23280a94387ba10ca266c311135ca57a"}, + {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72f638b6e56480c588580d10e3185dc9770d66a2bee49735ae6ac4277eb701da"}, + {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1802d92ba8b97a758f61f6aef8324c7705fe35a4b32ce9ed1569a8ddb43f0e33"}, + {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cdcd0b5f6952ed7187f46a7f0ea72436ddbd7111af2263adcbfe2850d0c26e7"}, + {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db83ad25c3ea908f1c983b4b28ab5b9408a18962fa21724a5d1d65887842462d"}, + {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98afc87b14868bb51f478f01c4d1578a190efb60c1b44dd7fc6dea7a61764889"}, + {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2ad866b82dd457239e2d0de864a09cc6efd4ccd2e0706314368835773e2b6018"}, + {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:c9778c56a4f9cda3c61941ea95db279b52c38e9b9ad174d52fd7174f9ec44c46"}, + {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:da810a6704723bbaae3d9d5a0c552c52e15f2b97aff599c5243464e04a9bc5ac"}, + {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e38fe19d4f6ddc669f8a13fa21b482ec72bc32d2def6b97018dba4db53823848"}, + {file = "bitarray-2.7.6-cp38-cp38-win32.whl", hash = "sha256:17c13936465e4dd2f4b58ce93f4d1fc92a684f1870830581bf950991e1e56eaa"}, + {file = "bitarray-2.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:732642e9284a7dbaca00d8a4837f997462fc01e8207f2be598e179c3b51e20e2"}, + {file = "bitarray-2.7.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1b8f439bf3939133ca2a12bfdc6fc2408b50099fd8fec88e5829f1eb9d49d636"}, + {file = "bitarray-2.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97496cca479e2b30a5c5354359f1fb85c4a0678b8d651656cc96a6ba3028bc94"}, + {file = "bitarray-2.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7a8d697e201a2f9b16d292d7fa066b9bbc0123abd9058885e408db984a1b4776"}, + {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:249e5d52785de2615264e579e25d85038e4bea9797e24e47b577e614c1187a2d"}, + {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e1da9ebd148389bce250403c02ef1f1495984612db534072b0aa101fd051a0b"}, + {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c237144123f1eb563c305d70634a97729716bfcd841f1519c33396e62d8ea3bd"}, + {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e90774f5453c610a0b62f6d235120c459c1dc0f2c7e8b4871a14f2dcf4d74cc"}, + {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07ac65cae21d79a4642a85109e40da48903c58fd14b7e42f31c48c37b7b8a37a"}, + {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eeeb25912fc4832546d328032e345d281720c2c73effd2234feb24045cfb10e"}, + {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37429158856fe0548786910ebc16e56606a60f01946f746cb7a6a78acf787711"}, + {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:5ecdb1f22f9aae4405393cf845b1a43bbf786536fa673aae92745508ca8c84ef"}, + {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1bc7466820fae5376e2c0c9592e67ffd2a5fb1ab261942357ba23b5252b5eae5"}, + {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:900cc391b38bf098f30ae5847c2df30f413b9311f27f13ced1818cbc28da928c"}, + {file = "bitarray-2.7.6-cp39-cp39-win32.whl", hash = "sha256:17d2df5517ab961b6ce679c050884141439695a931539597fea76933891f04a0"}, + {file = "bitarray-2.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:66ebfee12371d9cf7f59eb8964fcb1e711f075df6a2e29880c9aa8b55fa5bce4"}, + {file = "bitarray-2.7.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17df9b29105beec4dc4a489041783ddfe9de2628706bd4255863acfe3a78e648"}, + {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffdb9106fd0d36b0a87db9903e3078877a9cdde98e064b76f24f6d8eb46067e6"}, + {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689859e796d39d0151d1d7a0f50b8119547cbd76d815884e8d8ff5e7e3ce8244"}, + {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bff837274244e1dcf57e43820ccd8f95736ab30e7f5f0c79e81f655717060d37"}, + {file = "bitarray-2.7.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ffd1d8edb67b8ca264e033a349b611426b5b84c3eec410aeb6df67b784450131"}, + {file = "bitarray-2.7.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:915abc5db73dd2e15182bc2b281e0aa565124068a6f36f8237d3479804478ea8"}, + {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90a246583ff481c869227058988f5d2cfe5dd5a6cf76596b6e8de88cc10d440a"}, + {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24f7804bd40deba52fb14c5c1510273e6e4a084ef7c9896a30bd9ee5353dbc40"}, + {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e4f09d684337e33f6ad24bf4cda984de07b969865bfe2a8fd556ee1e1581cc"}, + {file = "bitarray-2.7.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bbb093c86e2c747ba3e81b01924353d982d4f6b62cce76a5eb1eb157cdf499e9"}, + {file = "bitarray-2.7.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13734e42f83a7ef75c876458619e11863f1d60644c83ff9a5ce21abf2935f331"}, + {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439459b58ff0cd29883445662c5d80cee109c4ce5984001798bb74a31b1ac9e"}, + {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952170c03bc415719156594aed10c7b20ce7d4389551f6bc89969e23486d245c"}, + {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a13774c45e9f59941b374a0e489f6660fcb4d027005748036a829cfab097c14"}, + {file = "bitarray-2.7.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2d6a93eb2af22deba0e6f7353a68457fa04f933d59c6a0ca420759c0527850cd"}, + {file = "bitarray-2.7.6.tar.gz", hash = "sha256:3807f9323c308bc3f9b58cbe5d04dc28f34ac32d852999334da96b42f64b5356"}, +] + +[[package]] +name = "black" +version = "22.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "build" +version = "0.10.0" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, + {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +packaging = ">=19.0" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"] +test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"] +typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "cachecontrol" +version = "0.12.14" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.6" +files = [ + {file = "CacheControl-0.12.14-py2.py3-none-any.whl", hash = "sha256:1c2939be362a70c4e5f02c6249462b3b7a24441e4f1ced5e9ef028172edf356a"}, + {file = "CacheControl-0.12.14.tar.gz", hash = "sha256:d1087f45781c0e00616479bfd282c78504371ca71da017b49df9f5365a95feba"}, +] + +[package.dependencies] +lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = "*" + +[package.extras] +filecache = ["lockfile (>=0.9)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "cleo" +version = "2.0.1" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.0.1-py3-none-any.whl", hash = "sha256:6eb133670a3ed1f3b052d53789017b6e50fca66d1287e6e6696285f4cb8ea448"}, + {file = "cleo-2.0.1.tar.gz", hash = "sha256:eb4b2e1f3063c11085cebe489a6e9124163c226575a3c3be69b2e51af4a15ec5"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=2.2.0,<3.0.0" + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + +[[package]] +name = "cryptography" +version = "41.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699"}, + {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a"}, + {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca"}, + {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43"}, + {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b"}, + {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3"}, + {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db"}, + {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31"}, + {file = "cryptography-41.0.1-cp37-abi3-win32.whl", hash = "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5"}, + {file = "cryptography-41.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c"}, + {file = "cryptography-41.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb"}, + {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3"}, + {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039"}, + {file = "cryptography-41.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc"}, + {file = "cryptography-41.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485"}, + {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c"}, + {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a"}, + {file = "cryptography-41.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5"}, + {file = "cryptography-41.0.1.tar.gz", hash = "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cytoolz" +version = "0.12.1" +description = "Cython implementation of Toolz: High performance functional utilities" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cytoolz-0.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c59bb4ca88e1c69931468bf21f91c8f64d8bf1999eb163b7a2df336f60c304a"}, + {file = "cytoolz-0.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d700e011156ff112966c6d77faaae125fcaf538f4cec2b9ce8957de82858f0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3f57c48eb939d2986eba4aeaeedf930ebf94d58c91a42d4e0fc45ed5427dc"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25ff13c468c06da9ef26651dc389e7e8bb7af548f8c1dfb96305f57f18d398a8"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a734511144309ea6e105406633affb74e303a3df07d8a3954f9b01946e27ecb1"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48bc2f30d1b2646d675bb8e7778ab59379bf9edc59fe06fb0e7f85ba1271bf44"}, + {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30936ae8fa68b6a1ac8ad6c4bacb5a8a00d51bc6c89f9614a1557b0105d09f8a"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efd1b2da3ee577fcfa723a214f73186aef9674dd5b28242d90443c7a82722b0f"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6805b007af3557ee6c20dab491b6e55a8177f5b6845d9e6c653374d540366ba7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a6e63fc67b23830947b51e0a488992e3c904fce825ead565f3904dcf621d05f7"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9e324a94856d88ecf10f34c102d0ded67d7c3cf644153d77e34a29720ce6aa47"}, + {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02975e2b1e61e47e9afa311f4c1783d155136fad37c54a1cebfe991c5a0798a1"}, + {file = "cytoolz-0.12.1-cp310-cp310-win32.whl", hash = "sha256:b6569f6038133909cd658dbdcc6fc955f791dc47a7f5b55d2066f742253dcbfe"}, + {file = "cytoolz-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:1be368623e46ad3c1ce807e7a436acb119c26001507b31f92ceb21b86e08c386"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:849f461bffa1e7700ccfcb5186df29cd4cdcc9efdb7199cb8b5681dc37045d72"}, + {file = "cytoolz-0.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4284120c978fb7039901bf6e66832cb3e82ac1b2a107512e735bdb04fd5533ed"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ec296f01c29c809698eaf677211b6255691295c2b35caab2131e1e7eaadfbac"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37c53f456a1c84566a7d911eec57c4c6280b915ab0600e7671582793cc2769fe"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b6761791973b1e839b8309d5853b40eeb413368e31beaf5f2b6ed44c6fc7cf0"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff478682e8ee6dbaa37201bb71bf4a6eee744006ab000e8f5cea05066fc7c845"}, + {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:867bebe6be30ee36a836f9b835790762a74f46be8cc339ea57b68dcecdbc1133"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7e903df991f0957e2b271a37bb25d28e0d260c52825ae67507d15ca55a935961"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e797c4afb1b7962d3205b1959e1051f7e6bfbba29da44042a9efc2391f1feb38"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b8eceaa12b7f152b046b67cb053ec2b5b00f73593983de69bc5e63a8aca4a7a8"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b575393dd431b8e211de35bd593d831dac870172b16e2b7934f3566b8fc89377"}, + {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3032c0ba42dee5836d6b57a72a569b65df2c29e8ed266cb900d569003cf933a9"}, + {file = "cytoolz-0.12.1-cp311-cp311-win32.whl", hash = "sha256:c576bd63495150385b8d05eaae775387f378be2fd9805d3ffb4d17c87271fbad"}, + {file = "cytoolz-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:421b224dc4157a0d66625acb5798cf50858cfa06a5232d39a8bd6cf1fa88aca3"}, + {file = "cytoolz-0.12.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:be5a454a95797343d0fb1ed02caecae73a023b1393c112951c84f17ec9f4076c"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061387aa39b9c1576c25d0c59142513c09e77a2a07bd5d6211a43c7a758b6f45"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f4dbc3f0ec8f6fc68865489af21dcf042ff007d2737c27bfd73296f15db544"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a816bff6bf424753e1ac2441902ceaf37ae6718b745a53f6aa1a60c617fb4f5f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633f19d1990b1cf9c67dce9c28bf8b5a18e42785d15548607a100e1236384d5d"}, + {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fa7009c843667868aa8bdb3d68e5ef3d6356dd418b17ed5ca4e1340e82483a5"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1c29dd04e282ddfd45b457e3551075beec9128aa9271245e58ce924bf6e055f8"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd35c0be4c46274129dd1678bb911dd4e93d23968b26f4e39cd55bc7cb3b1bac"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5158ae6d8dd112d003f677039a3613ca7d2592bfe35d7accf23684edb961fc26"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7eb9e6fa8a82c3d2f519f7d3942898a97792e3895569e9501b9431048289b82f"}, + {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ac6784cc43aec51a86cf9058a2a343084f8cf46a9281bea5762bfa608127c53b"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:794cce219bbcb2f36ca220f27d5afd64eaa854e04901bd6f240be156a578b607"}, + {file = "cytoolz-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:695dd8231e4f1bfb9a2363775a6e4e56ad9d2058058f817203a49614f4bfe33b"}, + {file = "cytoolz-0.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1bd8017ef0da935a20106272c5f5ff6b1114add1ccb09cfed1ff7ec5cc01c6d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e1ebf6eb4438b8c45cbe7e7b22fc65df0c9efa97a70d3bf2f51e08b19756a5"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:816c2038008ebf50d81171ddfae377f1af9e71d504ec609469dcb0906bfcf2ae"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bebe58f7a160db7838eb70990c704db4bdc2d58bd364290fd69be0587be8bac"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72440305f634604827f96810e4469877b89f5c060d6852267650a49b0e3768c"}, + {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46ebc463bb45f278a2b94e630061c26e10077cb68d4c93583d8f4199699a5ef"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e75e287787e6adafed9d8c3d3e7647c0b5eb460221f9f92d7dfe48b45ba77c0d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:03ab22c9aeb1535f8647d23b6520b0c3d41aaa18d04ef42b352dde1931f2e2b1"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b2ac288f27a2689d9e39f4cf4df5437a8eb038eaae515169586c77f9f8fb343a"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97a24c0d0806fcf9a6e75fc18aeb95adc37eb0baf6451f10a2de23ffd815329d"}, + {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:42c9e5cd2a48a257b1f2402334b48122501f249b8dcf77082f569f2680f185eb"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:35fae4eaa0eaf9072a5fe2d244a79e65baae4e5ddbe9cc629c5037af800213a2"}, + {file = "cytoolz-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5af43ca7026ead3dd08b261e4f7163cd2cf3ceaa74fa5a81f7b7ea5d445e41d6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fcc378fa97f02fbcef090b3611305425d72bd1c0afdd13ef4a82dc67d40638b6"}, + {file = "cytoolz-0.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc3645cf6b9246cb8e179db2803e4f0d148211d2a2cf22d5c9b5219111cd91a0"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b245b824f4705aef0e4a03fafef3ad6cb59ef43cc564cdbf683ee28dfc11ad5"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1964dcb5f250fd13fac210944b20810d61ef4094a17fbbe502ab7a7eaeeace7"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7194a22a4a24f3561cb6ad1cca9c9b2f2cf34cc8d4bce6d6a24c80960323fa8"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c5434db53f3a94a37ad8aedb231901e001995d899af6ed1165f3d27fa04a6a"}, + {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b30cd083ef8af4ba66d9fe5cc75c653ede3f2655f97a032db1a14cc8a006719c"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bef934bd3e024d512c6c0ad1c66eb173f61d9ccb4dbca8d75f727a5604f7c2f6"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37320669c364f7d370392af33cc1034b4563da66c22cd3261e3530f4d30dbe4b"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb95d23defb2322cddf70efb4af6dac191d95edaa343e8c1f58f1afa4f92ecd"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ac5895d5f78dbd8646fe37266655ba4995f9cfec38a86595282fee69e41787da"}, + {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:499af2aff04f65b4c23de1df08e1d1484a93b23ddaaa0163e44b5070b68356eb"}, + {file = "cytoolz-0.12.1-cp38-cp38-win32.whl", hash = "sha256:aa61e3da751a2dfe95aeca603f3ef510071a136ba9905f61ae6cb5d0696271ad"}, + {file = "cytoolz-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5b43ce952a5a31441556c55f5f5f5a8e62c28581a0ff2a2c31c04ef992d73bd"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b8f88251b84b3877254cdd59c86a1dc6b2b39a03c6c9c067d344ef879562e0"}, + {file = "cytoolz-0.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d72415b0110f7958dd3a5ee98a70166f47bd42ede85e3535669c794d06f57406"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8101ab6de5aa0b26a2b5032bc488d430010c91863e701812d65836b03a12f61"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eed428b5e68c28abf2c71195e799850e040d67a27c05f7785319c611665b86a"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59641eb1f41cb688b3cb2f98c9003c493a5024325f76b5c02333d08dd972127c"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a48940ff0449ffcf690310bf9228bb57885f7571406ed2fe05c98e299987195"}, + {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bae431a5985cdb2014be09d37206c288e0d063940cf9539e9769bd2ec26b220"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb8b10405960a8e6801a4702af98ea640130ec6ecfc1208195762de3f5503ba9"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c9a16a5b4f54d5c0a131f56b0ca65998a9a74958b5b36840c280edba4f8b907"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49911cb533c96d275e31e7eaeb0742ac3f7afe386a1d8c40937814d75039a0f7"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dbae37d48ef5a0ab90cfaf2b9312d96f034b1c828208a9cbe25377a1b19ba129"}, + {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c34e69be4429633fc614febe3127fa03aa418a1abb9252f29d9ba5b3394573a5"}, + {file = "cytoolz-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0d474dacbafbdbb44c7de986bbf71ff56ae62df0d52ab3b6fa966784dc88737a"}, + {file = "cytoolz-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:3d6d0b0075731832343eb88229cea4bf39e96f3fc7acbc449aadbdfec2842703"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8506d1863f30d26f577c4ed59d2cfd03d2f39569f9cbaa02a764a9de73d312d5"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a1eae39656a1685e8b3f433eecfd72015ce5c1d7519e9c8f9402153c68331bb"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a0055943074c6c85b77fcc3f42f7c54010a3478daa2ed9d6243d0411c84a4d3"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a7a325b8fe885a6dd91093616c703134f2dacbd869bc519970df3849c2a15b"}, + {file = "cytoolz-0.12.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7b60caf0fa5f1b49f1062f7dc0f66c7b23e2736bad50fa8296bfb845995e3051"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:980e7eb7205e01816a92f3290cfc80507957e64656b9271a0dfebb85fe3718c0"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d38a40fe153f23cda0e823413fe9d9ebee89dd461827285316eff929fb121e"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d540e9c34a61b53b6a374ea108794a48388178f7889d772e364cdbd6df37774c"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:117871f036926e42d3abcee587eafa9dc7383f1064ac53a806d33e76604de311"}, + {file = "cytoolz-0.12.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:31131b54a0c72efc0eb432dc66df546c6a54f2a7d396c9a34cf65ac1c26b1df8"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4534cbfad73cdb1a6dad495530d4186d57d73089c01e9cb0558caab50e46cb3b"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50db41e875e36aec11881b8b12bc69c6f4836b7dd9e88a9e5bbf26c2cb3ba6cd"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6716855f9c669c9e25a185d88e0f169839bf8553d16496796325acd114607c11"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f32452e833f0605b871626e6c61b71b0cba24233aad0e04accc3240497d4995"}, + {file = "cytoolz-0.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba74c239fc6cb6e962eabc420967c7565f3f363b776c89b3df5234caecf1f463"}, + {file = "cytoolz-0.12.1.tar.gz", hash = "sha256:fc33909397481c90de3cec831bfb88d97e220dc91939d996920202f184b4648e"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "dataclassy" +version = "0.11.1" +description = "A fast and flexible reimplementation of data classes" +optional = false +python-versions = ">=3.6" +files = [ + {file = "dataclassy-0.11.1-py3-none-any.whl", hash = "sha256:bcb030d3d700cf9b1597042bbc8375b92773e6f68f65675a7071862c0ddb87f5"}, + {file = "dataclassy-0.11.1.tar.gz", hash = "sha256:ad6622cb91e644d13f68768558983fbc22c90a8ff7e355638485d18b9baf1198"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "dulwich" +version = "0.21.5" +description = "Python Git Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8864719bc176cdd27847332a2059127e2f7bab7db2ff99a999873cb7fff54116"}, + {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3800cdc17d144c1f7e114972293bd6c46688f5bcc2c9228ed0537ded72394082"}, + {file = "dulwich-0.21.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2f676bfed8146966fe934ee734969d7d81548fbd250a8308582973670a9dab1"}, + {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db330fb59fe3b9d253bdf0e49a521739db83689520c4921ab1c5242aaf77b82"}, + {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e8f6d4f4f4d01dd1d3c968e486d4cd77f96f772da7265941bc506de0944ddb9"}, + {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1cc0c9ba19ac1b2372598802bc9201a9c45e5d6f1f7a80ec40deeb10acc4e9ae"}, + {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61e10242b5a7a82faa8996b2c76239cfb633620b02cdd2946e8af6e7eb31d651"}, + {file = "dulwich-0.21.5-cp310-cp310-win32.whl", hash = "sha256:7f357639b56146a396f48e5e0bc9bbaca3d6d51c8340bd825299272b588fff5f"}, + {file = "dulwich-0.21.5-cp310-cp310-win_amd64.whl", hash = "sha256:891d5c73e2b66d05dbb502e44f027dc0dbbd8f6198bc90dae348152e69d0befc"}, + {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45d6198e804b539708b73a003419e48fb42ff2c3c6dd93f63f3b134dff6dd259"}, + {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c2a565d4e704d7f784cdf9637097141f6d47129c8fffc2fac699d57cb075a169"}, + {file = "dulwich-0.21.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:823091d6b6a1ea07dc4839c9752198fb39193213d103ac189c7669736be2eaff"}, + {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2c9931b657f2206abec0964ec2355ee2c1e04d05f8864e823ffa23c548c4548"}, + {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dc358c2ee727322a09b7c6da43d47a1026049dbd3ad8d612eddca1f9074b298"}, + {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6155ab7388ee01c670f7c5d8003d4e133eebebc7085a856c007989f0ba921b36"}, + {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a605e10d72f90a39ea2e634fbfd80f866fc4df29a02ea6db52ae92e5fd4a2003"}, + {file = "dulwich-0.21.5-cp311-cp311-win32.whl", hash = "sha256:daa607370722c3dce99a0022397c141caefb5ed32032a4f72506f4817ea6405b"}, + {file = "dulwich-0.21.5-cp311-cp311-win_amd64.whl", hash = "sha256:5e56b2c1911c344527edb2bf1a4356e2fb7e086b1ba309666e1e5c2224cdca8a"}, + {file = "dulwich-0.21.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:85d3401d08b1ec78c7d58ae987c4bb7b768a438f3daa74aeb8372bebc7fb16fa"}, + {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90479608e49db93d8c9e4323bc0ec5496678b535446e29d8fd67dc5bbb5d51bf"}, + {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a6bf99f57bcac4c77fc60a58f1b322c91cc4d8c65dc341f76bf402622f89cb"}, + {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3e68b162af2aae995355e7920f89d50d72b53d56021e5ac0a546d493b17cbf7e"}, + {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0ab86d6d42e385bf3438e70f3c9b16de68018bd88929379e3484c0ef7990bd3c"}, + {file = "dulwich-0.21.5-cp37-cp37m-win32.whl", hash = "sha256:f2eeca6d61366cf5ee8aef45bed4245a67d4c0f0d731dc2383eabb80fa695683"}, + {file = "dulwich-0.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:1b20a3656b48c941d49c536824e1e5278a695560e8de1a83b53a630143c4552e"}, + {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3932b5e17503b265a85f1eda77ede647681c3bab53bc9572955b6b282abd26ea"}, + {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6616132d219234580de88ceb85dd51480dc43b1bdc05887214b8dd9cfd4a9d40"}, + {file = "dulwich-0.21.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaf6c7fb6b13495c19c9aace88821c2ade3c8c55b4e216cd7cc55d3e3807d7fa"}, + {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be12a46f73023970125808a4a78f610c055373096c1ecea3280edee41613eba8"}, + {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baecef0d8b9199822c7912876a03a1af17833f6c0d461efb62decebd45897e49"}, + {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:82f632afb9c7c341a875d46aaa3e6c5e586c7a64ce36c9544fa400f7e4f29754"}, + {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82cdf482f8f51fcc965ffad66180b54a9abaea9b1e985a32e1acbfedf6e0e363"}, + {file = "dulwich-0.21.5-cp38-cp38-win32.whl", hash = "sha256:c8ded43dc0bd2e65420eb01e778034be5ca7f72e397a839167eda7dcb87c4248"}, + {file = "dulwich-0.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:2aba0fdad2a19bd5bb3aad6882580cb33359c67b48412ccd4cfccd932012b35e"}, + {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd4ad079758514375f11469e081723ba8831ce4eaa1a64b41f06a3a866d5ac34"}, + {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fe62685bf356bfb4d0738f84a3fcf0d1fc9e11fee152e488a20b8c66a52429e"}, + {file = "dulwich-0.21.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aae448da7d80306dda4fc46292fed7efaa466294571ab3448be16714305076f1"}, + {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b24cb1fad0525dba4872e9381bc576ea2a6dcdf06b0ed98f8e953e3b1d719b89"}, + {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e39b7c2c9bda6acae83b25054650a8bb7e373e886e2334721d384e1479bf04b"}, + {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26456dba39d1209fca17187db06967130e27eeecad2b3c2bbbe63467b0bf09d6"}, + {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:281310644e02e3aa6d76bcaffe2063b9031213c4916b5f1a6e68c25bdecfaba4"}, + {file = "dulwich-0.21.5-cp39-cp39-win32.whl", hash = "sha256:4814ca3209dabe0fe7719e9545fbdad7f8bb250c5a225964fe2a31069940c4cf"}, + {file = "dulwich-0.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:c922a4573267486be0ef85216f2da103fb38075b8465dc0e90457843884e4860"}, + {file = "dulwich-0.21.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e52b20c4368171b7d32bd3ab0f1d2402e76ad4f2ea915ff9aa73bc9fa2b54d6d"}, + {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeb736d777ee21f2117a90fc453ee181aa7eedb9e255b5ef07c51733f3fe5cb6"}, + {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8a79c1ed7166f32ad21974fa98d11bf6fd74e94a47e754c777c320e01257c6"}, + {file = "dulwich-0.21.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b943517e30bd651fbc275a892bb96774f3893d95fe5a4dedd84496a98eaaa8ab"}, + {file = "dulwich-0.21.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32493a456358a3a6c15bbda07106fc3d4cc50834ee18bc7717968d18be59b223"}, + {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa44b812d978fc22a04531f5090c3c369d5facd03fa6e0501d460a661800c7f"}, + {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f46bcb6777e5f9f4af24a2bd029e88b77316269d24ce66be590e546a0d8f7b7"}, + {file = "dulwich-0.21.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a917fd3b4493db3716da2260f16f6b18f68d46fbe491d851d154fc0c2d984ae4"}, + {file = "dulwich-0.21.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:684c52cff867d10c75a7238151ca307582b3d251bbcd6db9e9cffbc998ef804e"}, + {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9019189d7a8f7394df6a22cd5b484238c5776e42282ad5d6d6c626b4c5f43597"}, + {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:494024f74c2eef9988adb4352b3651ac1b6c0466176ec62b69d3d3672167ba68"}, + {file = "dulwich-0.21.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f9b6ac1b1c67fc6083c42b7b6cd3b211292c8a6517216c733caf23e8b103ab6d"}, + {file = "dulwich-0.21.5.tar.gz", hash = "sha256:70955e4e249ddda6e34a4636b90f74e931e558f993b17c52570fa6144b993103"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "eip712" +version = "0.2.1" +description = "eip712: Message classes for typed structured data hashing and signing in Ethereum" +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "eip712-0.2.1-py3-none-any.whl", hash = "sha256:c984c577358d1c7e5d4e52802bf4bd0432e965ba7326448998f95fcc1b6d5269"}, + {file = "eip712-0.2.1.tar.gz", hash = "sha256:3997dace7e581b66a84d106a10baac47a3f6c94095d79c7d0971ca0ede1926ad"}, +] + +[package.dependencies] +dataclassy = ">=0.8.2,<1" +eth-abi = ">=4.0.0,<5" +eth-account = ">=0.8.0,<0.9" +eth-hash = {version = "*", extras = ["pycryptodome"]} +eth-typing = ">=3.3.0,<4" +eth-utils = ">=2.1.0,<3" +hexbytes = ">=0.3.0,<1" + +[package.extras] +dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.1.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.1.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] +doc = ["Sphinx (>=5.3.0,<6)", "myst-parser (>=0.18.1,<0.19)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] +lint = ["black (>=23.1.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.1.1,<2)", "types-setuptools"] +release = ["setuptools", "twine", "wheel"] +test = ["hypothesis (>=6.70.0,<7)", "pytest (>=6.0,<8)", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "eth-abi" +version = "4.1.0" +description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +optional = false +python-versions = ">=3.7.2, <4" +files = [ + {file = "eth_abi-4.1.0-py3-none-any.whl", hash = "sha256:15f9870ca054c09a8e474d2d7e81aff0c32421aebdac896193183fc143e31b50"}, + {file = "eth_abi-4.1.0.tar.gz", hash = "sha256:fe738cdb24983adfe89abf727c723c288f8d0029e97fb08160b20bb5290ab475"}, +] + +[package.dependencies] +eth-typing = ">=3.0.0" +eth-utils = ">=2.0.0" +parsimonious = ">=0.9.0,<0.10.0" + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)"] +tools = ["hypothesis (>=4.18.2,<5.0.0)"] + +[[package]] +name = "eth-account" +version = "0.8.0" +description = "eth-account: Sign Ethereum transactions and messages with local private keys" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "eth-account-0.8.0.tar.gz", hash = "sha256:ccb2d90a16c81c8ea4ca4dc76a70b50f1d63cea6aff3c5a5eddedf9e45143eca"}, + {file = "eth_account-0.8.0-py3-none-any.whl", hash = "sha256:0ccc0edbb17021004356ae6e37887528b6e59e6ae6283f3917b9759a5887203b"}, +] + +[package.dependencies] +bitarray = ">=2.4.0,<3" +eth-abi = ">=3.0.1" +eth-keyfile = ">=0.6.0,<0.7.0" +eth-keys = ">=0.4.0,<0.5" +eth-rlp = ">=0.3.0,<1" +eth-utils = ">=2.0.0,<3" +hexbytes = ">=0.1.0,<1" +rlp = ">=1.0.0,<4" + +[package.extras] +dev = ["Sphinx (>=1.6.5,<5)", "black (>=22,<23)", "bumpversion (>=0.5.3,<1)", "coverage", "flake8 (==3.7.9)", "hypothesis (>=4.18.0,<5)", "ipython", "isort (>=4.2.15,<5)", "jinja2 (>=3.0.0,<3.1.0)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)", "tox (==3.25.0)", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<5)", "jinja2 (>=3.0.0,<3.1.0)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)"] +lint = ["black (>=22,<23)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)"] +test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.25.0)"] + +[[package]] +name = "eth-bloom" +version = "2.0.0" +description = "Python implementation of the Ethereum Trie structure" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "eth-bloom-2.0.0.tar.gz", hash = "sha256:73576828dff7566b9216403e0898966912f370bae5734241dd3f50ce5664a825"}, + {file = "eth_bloom-2.0.0-py3-none-any.whl", hash = "sha256:cc86ab9670577996f7fcb8445b7a164ecd211ac91d9c4c2b5a47678623419927"}, +] + +[package.dependencies] +eth-hash = {version = ">=0.4.0", extras = ["pycryptodome"]} + +[package.extras] +deploy = ["bumpversion", "wheel"] +dev = ["black (>=22.1.0)", "build", "bumpversion", "flake8 (>=3.8.3)", "hypothesis (>=3.31.2)", "isort (>=4.2.15)", "mypy (==0.910)", "pytest (>=6.2.5)", "tox (>=2.6.0)", "twine", "wheel"] +lint = ["black (>=22.1.0)", "flake8 (>=3.8.3)", "isort (>=4.2.15)", "mypy (==0.910)"] +test = ["hypothesis (>=3.31.2)", "pytest (>=6.2.5)", "tox (>=2.6.0)"] + +[[package]] +name = "eth-hash" +version = "0.5.2" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "eth-hash-0.5.2.tar.gz", hash = "sha256:1b5f10eca7765cc385e1430eefc5ced6e2e463bb18d1365510e2e539c1a6fe4e"}, + {file = "eth_hash-0.5.2-py3-none-any.whl", hash = "sha256:251f62f6579a1e247561679d78df37548bd5f59908da0b159982bf8293ad32f0"}, +] + +[package.dependencies] +pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keyfile" +version = "0.6.1" +description = "A library for handling the encrypted keyfiles used to store ethereum private keys." +optional = false +python-versions = "*" +files = [ + {file = "eth-keyfile-0.6.1.tar.gz", hash = "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c"}, + {file = "eth_keyfile-0.6.1-py3-none-any.whl", hash = "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560"}, +] + +[package.dependencies] +eth-keys = ">=0.4.0,<0.5.0" +eth-utils = ">=2,<3" +pycryptodome = ">=3.6.6,<4" + +[package.extras] +dev = ["bumpversion (>=0.5.3,<1)", "eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "flake8 (==4.0.1)", "idna (==2.7)", "pluggy (>=1.0.0,<2)", "pycryptodome (>=3.6.6,<4)", "pytest (>=6.2.5,<7)", "requests (>=2.20,<3)", "setuptools (>=38.6.0)", "tox (>=2.7.0)", "twine", "wheel"] +keyfile = ["eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "pycryptodome (>=3.6.6,<4)"] +lint = ["flake8 (==4.0.1)"] +test = ["pytest (>=6.2.5,<7)"] + +[[package]] +name = "eth-keys" +version = "0.4.0" +description = "Common API for Ethereum key operations." +optional = false +python-versions = "*" +files = [ + {file = "eth-keys-0.4.0.tar.gz", hash = "sha256:7d18887483bc9b8a3fdd8e32ddcb30044b9f08fcb24a380d93b6eee3a5bb3216"}, + {file = "eth_keys-0.4.0-py3-none-any.whl", hash = "sha256:e07915ffb91277803a28a379418bdd1fad1f390c38ad9353a0f189789a440d5d"}, +] + +[package.dependencies] +eth-typing = ">=3.0.0,<4" +eth-utils = ">=2.0.0,<3.0.0" + +[package.extras] +coincurve = ["coincurve (>=7.0.0,<16.0.0)"] +dev = ["asn1tools (>=0.146.2,<0.147)", "bumpversion (==0.5.3)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (>=3.0.1,<3.1)", "flake8 (==3.0.4)", "hypothesis (>=5.10.3,<6.0.0)", "mypy (==0.782)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)", "tox (==3.20.0)", "twine"] +eth-keys = ["eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)"] +lint = ["flake8 (==3.0.4)", "mypy (==0.782)"] +test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "factory-boy (>=3.0.1,<3.1)", "hypothesis (>=5.10.3,<6.0.0)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)"] + +[[package]] +name = "eth-rlp" +version = "0.3.0" +description = "eth-rlp: RLP definitions for common Ethereum objects in Python" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "eth-rlp-0.3.0.tar.gz", hash = "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6"}, + {file = "eth_rlp-0.3.0-py3-none-any.whl", hash = "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33"}, +] + +[package.dependencies] +eth-utils = ">=2.0.0,<3" +hexbytes = ">=0.1.0,<1" +rlp = ">=0.6.0,<4" + +[package.extras] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] +lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)"] +test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.14.6)"] + +[[package]] +name = "eth-stdlib" +version = "0.2.6" +description = "Ethereum Standard Library for Python" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "eth_stdlib-0.2.6-py3-none-any.whl", hash = "sha256:650c7a1e048d1fa6bd2345e5c08ac425ea14f74b11bf4108cee9d16d962d4325"}, + {file = "eth_stdlib-0.2.6.tar.gz", hash = "sha256:08c3508f4e8009ce29566e8b9c4e817418fec6b54eb1ddc5e6fd079337d5fb01"}, +] + +[package.dependencies] +safe-pysha3 = {version = ">=1.0.3,<2.0.0", markers = "python_version >= \"3.9\""} + +[package.extras] +hypothesis = ["hypothesis (>=6.58.0,<7.0.0)"] + +[[package]] +name = "eth-typing" +version = "3.4.0" +description = "eth-typing: Common type annotations for ethereum python packages" +optional = false +python-versions = ">=3.7.2, <4" +files = [ + {file = "eth-typing-3.4.0.tar.gz", hash = "sha256:7f49610469811ee97ac43eaf6baa294778ce74042d41e61ecf22e5ebe385590f"}, + {file = "eth_typing-3.4.0-py3-none-any.whl", hash = "sha256:347d50713dd58ab50063b228d8271624ab2de3071bfa32d467b05f0ea31ab4c5"}, +] + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-utils" +version = "2.1.1" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "eth-utils-2.1.1.tar.gz", hash = "sha256:7cccfb0b0749431d0d001e327e9a7289bf07308316a73850ae3895020e5682f4"}, + {file = "eth_utils-2.1.1-py3-none-any.whl", hash = "sha256:4938ab742f91cdf19bae024261af090664f63ccf83bdb1213e7146c14209e899"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=3.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==3.8.3)", "hypothesis (>=4.43.0)", "ipython", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] +test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + +[[package]] +name = "fancycompleter" +version = "0.9.1" +description = "colorful TAB completion for Python prompt" +optional = false +python-versions = "*" +files = [ + {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, + {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, +] + +[package.dependencies] +pyreadline = {version = "*", markers = "platform_system == \"Windows\""} +pyrepl = ">=0.8.2" + +[[package]] +name = "filelock" +version = "3.12.2" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.6" +files = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "hexbytes" +version = "0.3.1" +description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59"}, + {file = "hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d"}, +] + +[package.extras] +dev = ["black (>=22)", "bumpversion (>=0.5.3)", "eth-utils (>=1.0.1,<3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=22)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)"] +test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "hypothesis" +version = "6.79.3" +description = "A library for property-based testing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hypothesis-6.79.3-py3-none-any.whl", hash = "sha256:245bed0fcf7612caa0ca1ecaa5c1e3a7100bbf9fd0fe4a24bdd9e46249b2774f"}, + {file = "hypothesis-6.79.3.tar.gz", hash = "sha256:69b55ee1dae2c7edd214e273a977d0dfba542946a211c9ef1f958743b49e430e"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.16.0)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] +cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=3.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark (>=0.10.1)"] +numpy = ["numpy (>=1.16.0)"] +pandas = ["pandas (>=1.1)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + +[[package]] +name = "ipython" +version = "8.14.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, + {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jaraco-classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jedi" +version = "0.18.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jsonschema" +version = "4.17.3" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "keyring" +version = "23.13.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.7" +files = [ + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "lark" +version = "1.1.5" +description = "a modern parsing library" +optional = false +python-versions = "*" +files = [ + {file = "lark-1.1.5-py3-none-any.whl", hash = "sha256:8476f9903e93fbde4f6c327f74d79e9b4bd0ed9294c5dfa3164ab8c581b5de2a"}, + {file = "lark-1.1.5.tar.gz", hash = "sha256:4b534eae1f9af5b4ea000bea95776350befe1981658eea3820a01c37e504bb4d"}, +] + +[package.extras] +atomic-cache = ["atomicwrites"] +nearley = ["js2py"] +regex = ["regex"] + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + +[[package]] +name = "lru-dict" +version = "1.2.0" +description = "An Dict like LRU container." +optional = false +python-versions = "*" +files = [ + {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, + {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, + {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, + {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, + {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, + {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, + {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, + {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, + {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, + {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, + {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, + {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, + {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, + {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, + {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "mamushi" +version = "0.0.2a1" +description = "Vyper formatter" +optional = false +python-versions = ">=3.9" +files = [ + {file = "mamushi-0.0.2a1-py3-none-any.whl", hash = "sha256:79442cc696597edd28c4f6293cc9dfc9e45b15eb6230a42a088257f4ae5fe3fb"}, + {file = "mamushi-0.0.2a1.tar.gz", hash = "sha256:db98abf0f63dea3bc6d83a7bdc1e04a3ccda2060bb0f8063eff69b4028b6ece0"}, +] + +[package.dependencies] +click = ">=8.0.0" +lark = ">=1.0.0" +pathspec = ">=0.9.0" + +[package.extras] +dev = ["black (>=22.6.0)", "bump2version (>=1.0.0)", "coverage (>=6.0.0)", "flake8 (==5.0.4)", "mypy (>=0.900)", "mypy-extensions (==0.4.3)", "pylint (==2.14.5)", "pytest (>=6.0.0)", "pytest-cov (>=3.0.0)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "more-itertools" +version = "9.1.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, + {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, +] + +[[package]] +name = "msgpack" +version = "1.0.5" +description = "MessagePack serializer" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + +[[package]] +name = "mypy-extensions" +version = "0.4.4" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +optional = false +python-versions = ">=2.7" +files = [ + {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "parsimonious" +version = "0.9.0" +description = "(Soon to be) the fastest pure-Python PEG parser I could muster" +optional = false +python-versions = "*" +files = [ + {file = "parsimonious-0.9.0.tar.gz", hash = "sha256:b2ad1ae63a2f65bd78f5e0a8ac510a98f3607a43f1db2a8d46636a5d9e4a30c1"}, +] + +[package.dependencies] +regex = ">=2022.3.15" + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "pdbpp" +version = "0.10.3" +description = "pdb++, a drop-in replacement for pdb" +optional = false +python-versions = "*" +files = [ + {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, + {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, +] + +[package.dependencies] +fancycompleter = ">=0.8" +pygments = "*" +wmctrl = "*" + +[package.extras] +funcsigs = ["funcsigs"] +testing = ["funcsigs", "pytest"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "platformdirs" +version = "3.8.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, + {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry" +version = "1.5.1" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry-1.5.1-py3-none-any.whl", hash = "sha256:dfc7ce3a38ae216c0465694e2e674bef6eb1a2ba81aa47a26f9dc03362fe2f5f"}, + {file = "poetry-1.5.1.tar.gz", hash = "sha256:cc7ea4524d1a11558006224bfe8ba8ed071417d4eb5ef6c89decc6a37d437eeb"}, +] + +[package.dependencies] +build = ">=0.10.0,<0.11.0" +cachecontrol = {version = ">=0.12.9,<0.13.0", extras = ["filecache"]} +cleo = ">=2.0.0,<3.0.0" +crashtest = ">=0.4.1,<0.5.0" +dulwich = ">=0.21.2,<0.22.0" +filelock = ">=3.8.0,<4.0.0" +html5lib = ">=1.0,<2.0" +installer = ">=0.7.0,<0.8.0" +jsonschema = ">=4.10.0,<5.0.0" +keyring = ">=23.9.0,<24.0.0" +lockfile = ">=0.12.2,<0.13.0" +packaging = ">=20.4" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.9.4,<2.0.0" +platformdirs = ">=3.0.0,<4.0.0" +poetry-core = "1.6.1" +poetry-plugin-export = ">=1.4.0,<2.0.0" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.18,<3.0" +requests-toolbelt = ">=0.9.1,<2" +shellingham = ">=1.5,<2.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +urllib3 = ">=1.26.0,<2.0.0" +virtualenv = ">=20.22.0,<21.0.0" +xattr = {version = ">=0.10.0,<0.11.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.6.1" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_core-1.6.1-py3-none-any.whl", hash = "sha256:70707340447dee0e7f334f9495ae652481c67b32d8d218f296a376ac2ed73573"}, + {file = "poetry_core-1.6.1.tar.gz", hash = "sha256:0f9b0de39665f36d6594657e7d57b6f463cc10f30c28e6d1c3b9ff54c26c9ac3"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.4.0" +description = "Poetry plugin to export the dependencies to various formats" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "poetry_plugin_export-1.4.0-py3-none-any.whl", hash = "sha256:5d9186d6f77cf2bf35fc96bd11fe650cc7656e515b17d99cb65018d50ba22589"}, + {file = "poetry_plugin_export-1.4.0.tar.gz", hash = "sha256:f16974cd9f222d4ef640fa97a8d661b04d4fb339e51da93973f1bc9d578e183f"}, +] + +[package.dependencies] +poetry = ">=1.5.0,<2.0.0" +poetry-core = ">=1.6.0,<2.0.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.38" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, + {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + +[[package]] +name = "py-ecc" +version = "6.0.0" +description = "Elliptic curve crypto in python including secp256k1 and alt_bn128" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "py_ecc-6.0.0-py3-none-any.whl", hash = "sha256:54e8aa4c30374fa62d582c599a99f352c153f2971352171318bd6910a643be0b"}, + {file = "py_ecc-6.0.0.tar.gz", hash = "sha256:3fc8a79e38975e05dc443d25783fd69212a1ca854cc0efef071301a8f7d6ce1d"}, +] + +[package.dependencies] +cached-property = ">=1.5.1,<2" +eth-typing = ">=3.0.0,<4" +eth-utils = ">=2.0.0,<3" +mypy-extensions = ">=0.4.1" + +[package.extras] +dev = ["bumpversion (>=0.5.3,<1)", "flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)", "pytest (==6.2.5)", "pytest-xdist (==1.26.0)", "twine"] +lint = ["flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)"] +test = ["pytest (==6.2.5)", "pytest-xdist (==1.26.0)"] + +[[package]] +name = "py-evm" +version = "0.7.0a3" +description = "Python implementation of the Ethereum Virtual Machine" +optional = false +python-versions = "*" +files = [ + {file = "py-evm-0.7.0a3.tar.gz", hash = "sha256:afc845a5c79a592da4d2785dc4c1522a757b3f9dcebfca9da1f9116aa1a5b2ad"}, + {file = "py_evm-0.7.0a3-py3-none-any.whl", hash = "sha256:d9e53393fc8cab155d024f335100a257d1c524f0ebd66919cf97d4b54585af1a"}, +] + +[package.dependencies] +cached-property = ">=1.5.1,<2" +eth-bloom = ">=1.0.3" +eth-keys = ">=0.4.0,<0.5.0" +eth-typing = ">=3.3.0,<4.0.0" +eth-utils = ">=2.0.0,<3.0.0" +lru-dict = ">=1.1.6" +mypy-extensions = ">=0.4.1,<1.0.0" +py-ecc = ">=1.4.7,<7.0.0" +pyethash = ">=0.1.27,<1.0.0" +rlp = ">=3,<4" +trie = ">=2.0.0,<3" + +[package.extras] +benchmark = ["termcolor (>=1.1.0,<2.0.0)", "web3 (>=4.1.0,<5.0.0)"] +dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==0.971)", "mypy-extensions (>=0.4.1,<1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pyethash (>=0.1.27,<1.0.0)", "pysha3 (>=1.0.0,<2.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] +docs = ["Sphinx (>=1.5.5,<2)", "jinja2 (>=3.0.0,<3.1.0)", "py-evm (>=0.2.0-a.14)", "pysha3 (>=1.0.0,<2.0.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)"] +eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=0.4.1,<1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "pyethash (>=0.1.27,<1.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] +eth-extra = ["blake2b-py (>=0.1.4,<0.2)", "coincurve (>=13.0.0,<14.0.0)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "plyvel (>=1.2.0,<2)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "types-setuptools"] +test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)"] + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pycryptodome" +version = "3.18.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, + {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, + {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, + {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, + {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, + {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, + {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, + {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, + {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, + {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, + {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, +] + +[[package]] +name = "pyethash" +version = "0.1.27" +description = "Python wrappers for ethash, the ethereum proof of workhashing function" +optional = false +python-versions = "*" +files = [ + {file = "pyethash-0.1.27.tar.gz", hash = "sha256:ff66319ce26b9d77df1f610942634dac9742e216f2c27b051c0a2c2dec9c2818"}, +] + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] + +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "pyreadline" +version = "2.1" +description = "A python implmementation of GNU readline." +optional = false +python-versions = "*" +files = [ + {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, +] + +[[package]] +name = "pyrepl" +version = "0.9.0" +description = "A library for building flexible command line interfaces" +optional = false +python-versions = "*" +files = [ + {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, +] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "pytest" +version = "7.4.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-forked" +version = "1.6.0" +description = "run tests in isolated forked subprocesses" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-forked-1.6.0.tar.gz", hash = "sha256:4dafd46a9a600f65d822b8f605133ecf5b3e1941ebb3588e943b4e3eb71a5a3f"}, + {file = "pytest_forked-1.6.0-py3-none-any.whl", hash = "sha256:810958f66a91afb1a1e2ae83089d8dc1cd2437ac96b12963042fbb9fb4d16af0"}, +] + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-repeat" +version = "0.9.1" +description = "pytest plugin for repeating tests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pytest-repeat-0.9.1.tar.gz", hash = "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b"}, + {file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"}, +] + +[package.dependencies] +pytest = ">=3.6" + +[[package]] +name = "pytest-xdist" +version = "3.3.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, + {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.1" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.1.tar.gz", hash = "sha256:934a2def1e5cbc472b2b6bf80680c0f03cd87df65dfd58bfd1846969de095b03"}, + {file = "pywin32_ctypes-0.2.1-py3-none-any.whl", hash = "sha256:b9a53ef754c894a525469933ab2a447c74ec1ea6b9d2ef446f40ec50d3dcec9f"}, +] + +[[package]] +name = "rapidfuzz" +version = "2.15.1" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc0bc259ebe3b93e7ce9df50b3d00e7345335d35acbd735163b7c4b1957074d3"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d59fb3a410d253f50099d7063855c2b95df1ef20ad93ea3a6b84115590899f25"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c525a3da17b6d79d61613096c8683da86e3573e807dfaecf422eea09e82b5ba6"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4deae6a918ecc260d0c4612257be8ba321d8e913ccb43155403842758c46fbe"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2577463d10811386e704a3ab58b903eb4e2a31b24dfd9886d789b0084d614b01"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f67d5f56aa48c0da9de4ab81bffb310683cf7815f05ea38e5aa64f3ba4368339"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7927722ff43690e52b3145b5bd3089151d841d350c6f8378c3cfac91f67573a"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6534afc787e32c4104f65cdeb55f6abe4d803a2d0553221d00ef9ce12788dcde"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d0ae6ec79a1931929bb9dd57bc173eb5ba4c7197461bf69e3a34b6dd314feed2"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be7ccc45c4d1a7dfb595f260e8022a90c6cb380c2a346ee5aae93f85c96d362b"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ba013500a2b68c64b2aecc5fb56a2dad6c2872cf545a0308fd044827b6e5f6a"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4d9f7d10065f657f960b48699e7dddfce14ab91af4bab37a215f0722daf0d716"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e24a1b802cea04160b3fccd75d2d0905065783ebc9de157d83c14fb9e1c6ce2"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-win32.whl", hash = "sha256:dffdf03499e0a5b3442951bb82b556333b069e0661e80568752786c79c5b32de"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d150d90a7c6caae7962f29f857a4e61d42038cfd82c9df38508daf30c648ae7"}, + {file = "rapidfuzz-2.15.1-cp310-cp310-win_arm64.whl", hash = "sha256:87c30e9184998ff6eb0fa9221f94282ce7c908fd0da96a1ef66ecadfaaa4cdb7"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6986413cb37035eb796e32f049cbc8c13d8630a4ac1e0484e3e268bb3662bd1b"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a72f26e010d4774b676f36e43c0fc8a2c26659efef4b3be3fd7714d3491e9957"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5cd54c98a387cca111b3b784fc97a4f141244bbc28a92d4bde53f164464112e"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7fac7c3da39f93e6b2ebe386ed0ffe1cefec91509b91857f6e1204509e931f"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f976e76ac72f650790b3a5402431612175b2ac0363179446285cb3c901136ca9"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abde47e1595902a490ed14d4338d21c3509156abb2042a99e6da51f928e0c117"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca8f1747007a3ce919739a60fa95c5325f7667cccf6f1c1ef18ae799af119f5e"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35da09ab9797b020d0d4f07a66871dfc70ea6566363811090353ea971748b5a"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3a769ca7580686a66046b77df33851b3c2d796dc1eb60c269b68f690f3e1b65"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d50622efefdb03a640a51a6123748cd151d305c1f0431af762e833d6ffef71f0"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b7461b0a7651d68bc23f0896bffceea40f62887e5ab8397bf7caa883592ef5cb"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:074ee9e17912e025c72a5780ee4c7c413ea35cd26449719cc399b852d4e42533"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7025fb105a11f503943f17718cdb8241ea3bb4d812c710c609e69bead40e2ff0"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-win32.whl", hash = "sha256:2084d36b95139413cef25e9487257a1cc892b93bd1481acd2a9656f7a1d9930c"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:5a738fcd24e34bce4b19126b92fdae15482d6d3a90bd687fd3d24ce9d28ce82d"}, + {file = "rapidfuzz-2.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:dc3cafa68cfa54638632bdcadf9aab89a3d182b4a3f04d2cad7585ed58ea8731"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c53d57ba7a88f7bf304d4ea5a14a0ca112db0e0178fff745d9005acf2879f7d"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6ee758eec4cf2215dc8d8eafafcea0d1f48ad4b0135767db1b0f7c5c40a17dd"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d93ba3ae59275e7a3a116dac4ffdb05e9598bf3ee0861fecc5b60fb042d539e"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c3ff75e647908ddbe9aa917fbe39a112d5631171f3fcea5809e2363e525a59d"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d89c421702474c6361245b6b199e6e9783febacdbfb6b002669e6cb3ef17a09"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f69e6199fec0f58f9a89afbbaea78d637c7ce77f656a03a1d6ea6abdc1d44f8"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:41dfea282844d0628279b4db2929da0dacb8ac317ddc5dcccc30093cf16357c1"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2dd03477feefeccda07b7659dd614f6738cfc4f9b6779dd61b262a73b0a9a178"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5efe035aa76ff37d1b5fa661de3c4b4944de9ff227a6c0b2e390a95c101814c0"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ed2cf7c69102c7a0a06926d747ed855bc836f52e8d59a5d1e3adfd980d1bd165"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a0e441d4c2025110ec3eba5d54f11f78183269a10152b3a757a739ffd1bb12bf"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-win32.whl", hash = "sha256:a4a54efe17cc9f53589c748b53f28776dfdfb9bc83619685740cb7c37985ac2f"}, + {file = "rapidfuzz-2.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bb8318116ecac4dfb84841d8b9b461f9bb0c3be5b616418387d104f72d2a16d1"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e9296c530e544f68858c3416ad1d982a1854f71e9d2d3dcedb5b216e6d54f067"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c4bcdb9238f11f8c4eba1b898937f09b92280d6f900023a8216008f299b41a"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb40a279e134bb3fef099a8b58ed5beefb201033d29bdac005bddcdb004ef71"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7381c11cb590bbd4e6f2d8779a0b34fdd2234dfa13d0211f6aee8ca166d9d05"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfdcdedfd12a0077193f2cf3626ff6722c5a184adf0d2d51f1ec984bf21c23c3"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85bece1ec59bda8b982bd719507d468d4df746dfb1988df11d916b5e9fe19e8"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b393f4a1eaa6867ffac6aef58cfb04bab2b3d7d8e40b9fe2cf40dd1d384601"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53de456ef020a77bf9d7c6c54860a48e2e902584d55d3001766140ac45c54bc7"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2492330bc38b76ed967eab7bdaea63a89b6ceb254489e2c65c3824efcbf72993"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:099e4c6befaa8957a816bdb67ce664871f10aaec9bebf2f61368cf7e0869a7a1"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:46599b2ad4045dd3f794a24a6db1e753d23304699d4984462cf1ead02a51ddf3"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:591f19d16758a3c55c9d7a0b786b40d95599a5b244d6eaef79c7a74fcf5104d8"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed17359061840eb249f8d833cb213942e8299ffc4f67251a6ed61833a9f2ea20"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-win32.whl", hash = "sha256:aa1e5aad325168e29bf8e17006479b97024aa9d2fdbe12062bd2f8f09080acf8"}, + {file = "rapidfuzz-2.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:c2bb68832b140c551dbed691290bef4ee6719d4e8ce1b7226a3736f61a9d1a83"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fac40972cf7b6c14dded88ae2331eb50dfbc278aa9195473ef6fc6bfe49f686"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0e456cbdc0abf39352800309dab82fd3251179fa0ff6573fa117f51f4e84be8"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:22b9d22022b9d09fd4ece15102270ab9b6a5cfea8b6f6d1965c1df7e3783f5ff"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46754fe404a9a6f5cbf7abe02d74af390038d94c9b8c923b3f362467606bfa28"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91abb8bf7610efe326394adc1d45e1baca8f360e74187f3fa0ef3df80cdd3ba6"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e40a2f60024f9d3c15401e668f732800114a023f3f8d8c40f1521a62081ff054"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a48ee83916401ac73938526d7bd804e01d2a8fe61809df7f1577b0b3b31049a3"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71580052f9dbac443c02f60484e5a2e5f72ad4351b84b2009fbe345b1f38422"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82b86d5b8c1b9bcbc65236d75f81023c78d06a721c3e0229889ff4ed5c858169"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fc4528b7736e5c30bc954022c2cf410889abc19504a023abadbc59cdf9f37cae"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e1e0e569108a5760d8f01d0f2148dd08cc9a39ead79fbefefca9e7c7723c7e88"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:94e1c97f0ad45b05003806f8a13efc1fc78983e52fa2ddb00629003acf4676ef"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47e81767a962e41477a85ad7ac937e34d19a7d2a80be65614f008a5ead671c56"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-win32.whl", hash = "sha256:79fc574aaf2d7c27ec1022e29c9c18f83cdaf790c71c05779528901e0caad89b"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:f3dd4bcef2d600e0aa121e19e6e62f6f06f22a89f82ef62755e205ce14727874"}, + {file = "rapidfuzz-2.15.1-cp39-cp39-win_arm64.whl", hash = "sha256:cac095cbdf44bc286339a77214bbca6d4d228c9ebae3da5ff6a80aaeb7c35634"}, + {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b89d1126be65c85763d56e3b47d75f1a9b7c5529857b4d572079b9a636eaa8a7"}, + {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7460e91168229768be882ea365ba0ac7da43e57f9416e2cfadc396a7df3c2"}, + {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c33c03e7092642c38f8a15ca2d8fc38da366f2526ec3b46adf19d5c7aa48ba"}, + {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040faca2e26d9dab5541b45ce72b3f6c0e36786234703fc2ac8c6f53bb576743"}, + {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6e2a3b23e1e9aa13474b3c710bba770d0dcc34d517d3dd6f97435a32873e3f28"}, + {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e597b9dfd6dd180982684840975c458c50d447e46928efe3e0120e4ec6f6686"}, + {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d14752c9dd2036c5f36ebe8db5f027275fa7d6b3ec6484158f83efb674bab84e"}, + {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558224b6fc6124d13fa32d57876f626a7d6188ba2a97cbaea33a6ee38a867e31"}, + {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c89cfa88dc16fd8c9bcc0c7f0b0073f7ef1e27cceb246c9f5a3f7004fa97c4d"}, + {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:509c5b631cd64df69f0f011893983eb15b8be087a55bad72f3d616b6ae6a0f96"}, + {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0f73a04135a03a6e40393ecd5d46a7a1049d353fc5c24b82849830d09817991f"}, + {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99d53138a2dfe8ada67cb2855719f934af2733d726fbf73247844ce4dd6dd5"}, + {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f01fa757f0fb332a1f045168d29b0d005de6c39ee5ce5d6c51f2563bb53c601b"}, + {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60368e1add6e550faae65614844c43f8a96e37bf99404643b648bf2dba92c0fb"}, + {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785744f1270828cc632c5a3660409dee9bcaac6931a081bae57542c93e4d46c4"}, + {file = "rapidfuzz-2.15.1.tar.gz", hash = "sha256:d62137c2ca37aea90a11003ad7dc109c8f1739bfbe5a9a217f3cdb07d7ac00f6"}, +] + +[package.extras] +full = ["numpy"] + +[[package]] +name = "regex" +version = "2023.6.3" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, + {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, + {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, + {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, + {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, + {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, + {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, + {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, + {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, + {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, + {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, + {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, + {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, + {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, + {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, + {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, + {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, + {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, + {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, + {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, + {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, + {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, + {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, + {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, + {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, + {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, + {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, + {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, + {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, + {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, + {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, + {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rich" +version = "13.4.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, + {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rlp" +version = "3.0.0" +description = "A package for Recursive Length Prefix encoding and decoding" +optional = false +python-versions = "*" +files = [ + {file = "rlp-3.0.0-py2.py3-none-any.whl", hash = "sha256:d2a963225b3f26795c5b52310e0871df9824af56823d739511583ef459895a7d"}, + {file = "rlp-3.0.0.tar.gz", hash = "sha256:63b0465d2948cd9f01de449d7adfb92d207c1aef3982f20310f8009be4a507e8"}, +] + +[package.dependencies] +eth-utils = ">=2.0.0,<3" + +[package.extras] +dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] +doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] +lint = ["flake8 (==3.4.1)"] +rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] +test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] + +[[package]] +name = "safe-pysha3" +version = "1.0.4" +description = "SHA-3 (Keccak) for Python 3.9 - 3.11" +optional = false +python-versions = "*" +files = [ + {file = "safe-pysha3-1.0.4.tar.gz", hash = "sha256:e429146b1edd198b2ca934a2046a65656c5d31b0ec894bbd6055127f4deaff17"}, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "shellingham" +version = "1.5.0.post1" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.0.post1-py2.py3-none-any.whl", hash = "sha256:368bf8c00754fd4f55afb7bbb86e272df77e4dc76ac29dbcbb81a59e9fc15744"}, + {file = "shellingham-1.5.0.post1.tar.gz", hash = "sha256:823bc5fb5c34d60f285b624e7264f4dda254bc803a3774a147bf99c0e3004a28"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "titanoboa" +version = "0.1.7" +description = "A Vyper interpreter" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +eth-abi = "*" +eth-account = "*" +eth-stdlib = "*" +eth-typing = "*" +hypothesis = "*" +py-evm = ">=0.7.0a2" +pytest = "*" +requests = "*" +rich = "*" +vyper = ">=0.3.8" + +[package.extras] +forking-recommended = ["plyvel", "ujson"] + +[package.source] +type = "git" +url = "https://github.com/vyperlang/titanoboa.git" +reference = "bd4af917754e7a66a84dee6134c8a42112224471" +resolved_reference = "bd4af917754e7a66a84dee6134c8a42112224471" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + +[[package]] +name = "toolz" +version = "0.12.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.5" +files = [ + {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, + {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, +] + +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "trie" +version = "2.1.1" +description = "Python implementation of the Ethereum Trie structure" +optional = false +python-versions = ">=3.7,<4" +files = [ + {file = "trie-2.1.1-py3-none-any.whl", hash = "sha256:c1a5fc17b37a75008a4517e4f297ad8026dce777eb0eed63ee6335c66d7437b7"}, + {file = "trie-2.1.1.tar.gz", hash = "sha256:1c7fa6f4a3088e083764cf4e32a07a69c672fcf15ad922e03f51158d64a855cf"}, +] + +[package.dependencies] +eth-hash = ">=0.1.0" +eth-utils = ">=2.0.0" +hexbytes = ">=0.2.0" +rlp = ">=3" +sortedcontainers = ">=2.1.0" + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash (>=0.1.0,<1.0.0)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=6.56.4,<7)", "isort (>=5.10.1)", "pycryptodome", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)"] +test = ["hypothesis (>=6.56.4,<7)", "pycryptodome", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "trove-classifiers" +version = "2023.5.24" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +files = [ + {file = "trove-classifiers-2023.5.24.tar.gz", hash = "sha256:fd5a1546283be941f47540a135bdeae8fb261380a6a204d9c18012f2a1b0ceae"}, + {file = "trove_classifiers-2023.5.24-py3-none-any.whl", hash = "sha256:d9d7ae14fb90bf3d50bef99c3941b176b5326509e6e9037e622562d6352629d0"}, +] + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.23.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, + {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.12,<4" +platformdirs = ">=3.5.1,<4" + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] + +[[package]] +name = "vyper" +version = "0.3.9" +description = "Vyper: the Pythonic Programming Language for the EVM" +optional = false +python-versions = ">=3.10,<4" +files = [ + {file = "vyper-0.3.9-py3-none-any.whl", hash = "sha256:eed86456068ddbf5329de831fb51c4e2741ac786d75bd86df925d1d0569ce8e9"}, + {file = "vyper-0.3.9.tar.gz", hash = "sha256:e140521f8a91060b32f953bd5f3a2c59e74ceb9475953a3d9a5d82f794fd3800"}, +] + +[package.dependencies] +asttokens = ">=2.0.5,<3" +importlib-metadata = "*" +pycryptodome = ">=3.5.1,<4" +semantic-version = ">=2.10,<3" +wheel = "*" + +[package.extras] +dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.910)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] +docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] +lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.910)"] +test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "wheel" +version = "0.40.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.40.0-py3-none-any.whl", hash = "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"}, + {file = "wheel-0.40.0.tar.gz", hash = "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)"] + +[[package]] +name = "wmctrl" +version = "0.4" +description = "A tool to programmatically control windows inside X" +optional = false +python-versions = "*" +files = [ + {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, +] + +[[package]] +name = "xattr" +version = "0.10.1" +description = "Python wrapper for extended filesystem attributes" +optional = false +python-versions = "*" +files = [ + {file = "xattr-0.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75"}, + {file = "xattr-0.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81"}, + {file = "xattr-0.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1"}, + {file = "xattr-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593"}, + {file = "xattr-0.10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f"}, + {file = "xattr-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b"}, + {file = "xattr-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c"}, + {file = "xattr-0.10.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db"}, + {file = "xattr-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab"}, + {file = "xattr-0.10.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a"}, + {file = "xattr-0.10.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4"}, + {file = "xattr-0.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d"}, + {file = "xattr-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5"}, + {file = "xattr-0.10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e"}, + {file = "xattr-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894"}, + {file = "xattr-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe"}, + {file = "xattr-0.10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7"}, + {file = "xattr-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c"}, + {file = "xattr-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f"}, + {file = "xattr-0.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4"}, + {file = "xattr-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4"}, + {file = "xattr-0.10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d"}, + {file = "xattr-0.10.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6"}, + {file = "xattr-0.10.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649"}, + {file = "xattr-0.10.1.tar.gz", hash = "sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5"}, +] + +[package.dependencies] +cffi = ">=1.0" + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "b5db3375dfda42b1e0e448285142ec0666eae8f249d7539d1ee7943cf6e7629b" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..63ff1317 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,60 @@ +[tool.poetry] +name = "stableswap-ng" +version = "0.1.0" +description = "Stableswap-ng environment for testing" +authors = ["Curve.fi"] +license = "MIT" +readme = "README.md" +packages = [] + +[tool.poetry.dependencies] +python = "^3.10" +poetry = "1.5.1" +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "bd4af917754e7a66a84dee6134c8a42112224471"} +vyper = "^0.3.9" + +[tool.poetry.group.dev.dependencies] +black = "22.3.0" +flake8 = "4.0.1" +isort = "5.12.0" +mamushi = "^0.0.2a1" + + +[tool.poetry.group.testing.dependencies] +eip712 = "^0.2.1" +eth-account = "~0.8.0" +ipython = "^8.14.0" +hypothesis = "^6.79.3" +pytest = "^7.4.0" +pytest-xdist = "^3.3.1" +pytest-forked = "^1.6.0" +pytest-repeat = "^0.9.1" +pdbpp = "^0.10.3" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | build + | dist + )/ +) +''' +line-length = 120 +target_version = ['py310'] + +[tool.isort] +profile = "black" +py_version = 310 +known_first_party = "poetry" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e6799ecd..00000000 --- a/requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -# linting: -black -flake8 -isort -mamushi -pip-tools -pre-commit - -# testing: -eip712 -eth_account -ipython -hypothesis -pytest -pytest-xdist -pytest-forked -pytest-repeat -pdbpp -hypothesis>=6.68.1 - -# vyper and dev framework: -git+https://github.com/vyperlang/titanoboa.git@bd4af917754e7a66a84dee6134c8a42112224471 -vyper>=0.3.9 From 018cc3ff6621b617c587a7216110790e160c9850 Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 26 Jun 2023 21:27:02 +0200 Subject: [PATCH 019/337] fix actions --- .github/workflows/.pre-commit-config.yaml | 5 ++++- .github/workflows/unit-tests.yaml | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/.pre-commit-config.yaml b/.github/workflows/.pre-commit-config.yaml index 81bd81e9..6b0f04fd 100644 --- a/.github/workflows/.pre-commit-config.yaml +++ b/.github/workflows/.pre-commit-config.yaml @@ -2,10 +2,13 @@ name: pre-commit on: [pull_request, push] +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 09c526b1..55b619c1 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -9,22 +9,24 @@ jobs: boa-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache Compiler Installations - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.vvm key: compiler-cache - name: Setup Python 3.10.4 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.10.4 - name: Install Requirements - run: pip install poetry==1.5.1 & poetry install + run: | + pip install poetry==1.5.1 + poetry install - name: Run Tests run: python -m pytest tests/boa -n auto From debaec2ddf089c4bbfa9e72cabbf0da3d53945b4 Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 26 Jun 2023 21:50:10 +0200 Subject: [PATCH 020/337] fix linters --- .flake8 | 2 ++ .../{.pre-commit-config.yaml => lint.yaml} | 16 ++++++++++------ .pre-commit-config.yaml | 1 + tests/fixtures/factory.py | 4 +--- tests/fixtures/tokens.py | 4 +--- .../pool/plain/test_add_liquidity_initial.py | 4 +--- 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 .flake8 rename .github/workflows/{.pre-commit-config.yaml => lint.yaml} (50%) diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..6deafc26 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/.github/workflows/.pre-commit-config.yaml b/.github/workflows/lint.yaml similarity index 50% rename from .github/workflows/.pre-commit-config.yaml rename to .github/workflows/lint.yaml index 6b0f04fd..153f6f3e 100644 --- a/.github/workflows/.pre-commit-config.yaml +++ b/.github/workflows/lint.yaml @@ -1,14 +1,18 @@ -name: pre-commit - on: [pull_request, push] -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +name: lint jobs: - pre-commit: + + lint: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + - uses: pre-commit/action@v3.0.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f585a7de..421b2beb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,7 @@ repos: rev: 4.0.1 hooks: - id: flake8 + args: [--max-line-length=120] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index e417619a..d8c719ea 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -61,7 +61,5 @@ def factory( @pytest.fixture(scope="module") -def factory_populated( - factory, swap_plain, swap_eth_rebasing, swap_oracle, swap_meta -): +def factory_populated(factory, swap_plain, swap_eth_rebasing, swap_oracle, swap_meta): return factory diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 27a693f0..8a3bad0d 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -24,9 +24,7 @@ def dai(deployer): def steth(deployer): with boa.env.prank(deployer): # TODO: turn this into a rebasing implementation - return boa.load( - "contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", 18 - ) + return boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", 18) @pytest.fixture(scope="module") diff --git a/tests/unitary/pool/plain/test_add_liquidity_initial.py b/tests/unitary/pool/plain/test_add_liquidity_initial.py index 6615df2a..8662f821 100644 --- a/tests/unitary/pool/plain/test_add_liquidity_initial.py +++ b/tests/unitary/pool/plain/test_add_liquidity_initial.py @@ -5,9 +5,7 @@ @pytest.mark.parametrize("min_amount", [0, 10**18]) -def test_initial( - alice, swap_plain, coins, decimals, min_amount, initial_amounts -): +def test_initial(alice, swap_plain, coins, decimals, min_amount, initial_amounts): amounts = [10**d for d in decimals] for idx in range(2): From ab22e557550e262b845b7b3efe8533ae48cc0dac Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 26 Jun 2023 22:03:22 +0200 Subject: [PATCH 021/337] fix tests action --- .github/workflows/{unit-tests.yaml => tests.yaml} | 7 ++++--- README.MD | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) rename .github/workflows/{unit-tests.yaml => tests.yaml} (87%) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/tests.yaml similarity index 87% rename from .github/workflows/unit-tests.yaml rename to .github/workflows/tests.yaml index 55b619c1..50ad98b0 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,4 +1,5 @@ -name: unit-tests-boa +# TODO: split actions later +name: tests on: ["push", "pull_request"] @@ -6,7 +7,7 @@ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - boa-tests: + tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -29,4 +30,4 @@ jobs: poetry install - name: Run Tests - run: python -m pytest tests/boa -n auto + run: pytest tests -n auto diff --git a/README.MD b/README.MD index 5a21422c..5d068cf0 100644 --- a/README.MD +++ b/README.MD @@ -9,6 +9,8 @@ pip install poetry==1.5.1 poetry install ``` +### Testing parameters + ```shell -python -m pytest tests/boa -n auto +pytest tests -n auto ``` From c4ff814498af579a78de5ea11908fc12706bb3cd Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 26 Jun 2023 22:09:06 +0200 Subject: [PATCH 022/337] fix testing action --- .github/workflows/tests.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 50ad98b0..96610ce5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -27,7 +27,10 @@ jobs: - name: Install Requirements run: | pip install poetry==1.5.1 - poetry install + poetry config virtualenvs.in-project true + poetry install --no-interaction - name: Run Tests - run: pytest tests -n auto + run: | + source .venv/bin/activate + pytest tests -n auto From 8330f1cff2134106c2edf651a418b852f8dd4a6a Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 27 Jun 2023 09:38:12 +0200 Subject: [PATCH 023/337] clean tests directory and rebase mock --- contracts/mocks/ERC20Oracle.vy | 18 ++- contracts/mocks/ERC20Rebase.vy | 127 ++++++++++++++++++ tests/fixtures/accounts.py | 57 +------- tests/fixtures/constants.py | 21 ++- tests/fixtures/factory.py | 5 - tests/fixtures/pools.py | 34 +---- tests/fixtures/tokens.py | 8 +- tests/unitary/factory/test_factory.py | 0 tests/unitary/pool/plain/conftest.py | 11 -- .../pool/plain/test_add_liquidity_initial.py | 31 ----- tests/utils/tokens.py | 32 +++-- 11 files changed, 193 insertions(+), 151 deletions(-) create mode 100644 contracts/mocks/ERC20Rebase.vy delete mode 100644 tests/unitary/factory/test_factory.py delete mode 100644 tests/unitary/pool/plain/conftest.py delete mode 100644 tests/unitary/pool/plain/test_add_liquidity_initial.py diff --git a/contracts/mocks/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy index bdd0f5a4..eee88a7b 100644 --- a/contracts/mocks/ERC20Oracle.vy +++ b/contracts/mocks/ERC20Oracle.vy @@ -1,7 +1,8 @@ # @version ^0.3.7 """ -@notice Mock ERC20 for testing +@notice Mock ERC20 with oracle +@dev This is for testing only, it is NOT safe for use """ @@ -24,7 +25,7 @@ balanceOf: public(HashMap[address, uint256]) allowances: HashMap[address, HashMap[address, uint256]] totalSupply: public(uint256) -exchange_rate: immutable(uint256) +exchange_rate: public(uint256) @external @@ -40,8 +41,7 @@ def __init__( assert _decimals == 18, "Decimals must be 18" self.decimals = _decimals - - exchange_rate = _exchange_rate + self.exchange_rate = _exchange_rate @external @@ -77,5 +77,11 @@ def approve(_spender: address, _value: uint256) -> bool: @external @view def exchangeRate() -> uint256: - # some arbitrary exchange rate: - return exchange_rate + rate: uint256 = self.exchange_rate + return rate + + +@external +def set_exchange_rate(rate: uint256) -> bool: + self.exchange_rate = rate + return True diff --git a/contracts/mocks/ERC20Rebase.vy b/contracts/mocks/ERC20Rebase.vy new file mode 100644 index 00000000..eef51920 --- /dev/null +++ b/contracts/mocks/ERC20Rebase.vy @@ -0,0 +1,127 @@ +# @version ^0.3.7 + +""" +@notice Rebase ERC20 mock +@dev This is for testing only, it is NOT safe for use +@dev Based on stEth implementation +""" + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event TransferShares: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) +allowances: HashMap[address, HashMap[address, uint256]] + +# <--- Rebase Parameters ---> +totalCoin: public(uint256) +totalShares: public(uint256) +shares: public(HashMap[address, uint256]) + + +@external +def __init__(_name: String[64], _symbol: String[32], _decimals: uint256): + self.name = _name + self.symbol = _symbol + self.decimals = _decimals + + +@external +@view +def totalSupply() -> uint256: + # Rebase is pegged to total pooled coin + return self.totalCoin + + +@external +@view +def balanceOf(_user: address) -> uint256: + return self._get_coins_by_shares(self.shares[_user]) + + +@external +@view +def allowance(_owner: address, _spender: address) -> uint256: + return self.allowances[_owner][_spender] + + +@external +def transfer(_to: address, _value: uint256) -> bool: + _shares: uint256 = self._get_shares_by_coins(_value) + + self.shares[msg.sender] -= _shares + self.shares[_to] += _shares + log Transfer(msg.sender, _to, _value) + log TransferShares(msg.sender, _to, _shares) + return True + + +@external +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + _shares: uint256 = self._get_shares_by_coins(_value) + + self.shares[_from] -= _shares + self.shares[_to] += _shares + self.allowances[_from][msg.sender] -= _value + + log Transfer(_from, _to, _value) + log TransferShares(_from, _to, _shares) + return True + + +@external +def approve(_spender: address, _value: uint256) -> bool: + self.allowances[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + return True + + +@internal +@view +def _share_price() -> uint256: + if self.totalShares == 0: + return 10 ** self.decimals + return self.totalCoin * 10 ** self.decimals / self.totalShares + + +@internal +@view +def _get_coins_by_shares(_shares: uint256) -> uint256: + return _shares * self._share_price() / 10 ** self.decimals + + +@internal +@view +def _get_shares_by_coins(_coins: uint256) -> uint256: + return _coins * 10 ** self.decimals / self._share_price() + + +@external +@view +def share_price() -> uint256: + return self._share_price() + + +@external +def set_total_coin(total_coin: uint256) -> bool: + assert self.totalShares != 0, "no shares" + + self.totalCoin = total_coin + return True diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 642d25d5..3b622c00 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -1,72 +1,29 @@ import boa import pytest -from eth_account.account import Account - -from tests.utils.tokens import mint_for_testing +from boa.environment import AddressType +from eth_account.account import Account, LocalAccount @pytest.fixture(scope="module") -def deployer(): +def deployer() -> AddressType: return boa.env.generate_address() @pytest.fixture(scope="module") -def owner(): +def owner() -> AddressType: return boa.env.generate_address() @pytest.fixture(scope="module") -def factory_admin(factory): +def factory_admin(factory) -> AddressType: return factory.admin() @pytest.fixture(scope="module") -def fee_receiver(): +def fee_receiver() -> AddressType: return boa.env.generate_address() @pytest.fixture(scope="module") -def user(): - acc = boa.env.generate_address() - boa.env.set_balance(acc, 10**25) - return acc - - -@pytest.fixture(scope="module") -def users(): - accs = [i() for i in [boa.env.generate_address] * 10] - for acc in accs: - boa.env.set_balance(acc, 10**25) - return accs - - -@pytest.fixture(scope="module") -def eth_acc(): +def eth_acc() -> LocalAccount: return Account.create() - - -@pytest.fixture(scope="module") -def alice(): - acc = boa.env.generate_address() - boa.env.set_balance(acc, 10**25) - return acc - - -@pytest.fixture(scope="module") -def loaded_alice(swap, alice): - mint_for_testing(swap, alice, 10**21) - return alice - - -@pytest.fixture(scope="module") -def bob(): - acc = boa.env.generate_address() - boa.env.set_balance(acc, 10**25) - return acc - - -@pytest.fixture(scope="module") -def charlie(): - acc = boa.env.generate_address() - boa.env.set_balance(acc, 10**25) - return acc diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 85c39d71..a51f3cf8 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -1,19 +1,26 @@ import pytest +INITIAL_AMOUNT = 1_000_000 + @pytest.fixture(scope="session") -def initial_amounts(decimals): - return [1_000_000 * 10**precision for precision in decimals] +def initial_amounts(decimals: list[int]) -> list[int]: + return [INITIAL_AMOUNT * 10**precision for precision in decimals] @pytest.fixture(scope="session") -def initial_amounts_underlying(underlying_decimals): - amts = [1_000_000 * 10**precision for precision in underlying_decimals] +def initial_amounts_underlying(underlying_decimals: list[int]) -> list[int]: + amts = [INITIAL_AMOUNT * 10**precision for precision in underlying_decimals] for i in range(1, len(underlying_decimals)): - amts[i] //= 3 + amts[i] //= len(underlying_decimals) - 1 return amts @pytest.fixture(scope="session") -def deposit_amounts(decimals): - return [1_000 * 10**precision for precision in decimals] +def deposit_amounts(decimals: list[int]) -> list[int]: + return [INITIAL_AMOUNT * 10**precision for precision in decimals] + + +@pytest.fixture(scope="session") +def zero_address() -> str: + return "0x0000000000000000000000000000000000000000" diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index d8c719ea..64db941e 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -58,8 +58,3 @@ def factory( # TODO: add Factory Meta Implementation return _factory - - -@pytest.fixture(scope="module") -def factory_populated(factory, swap_plain, swap_eth_rebasing, swap_oracle, swap_meta): - return factory diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index d7743bac..c5fd992d 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -4,29 +4,21 @@ # TODO: rebasing pool, meta pool -ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" - @pytest.fixture(scope="module") -def swap_plain( - factory, - dai, - usdc, - bob, - amm_implementation_plain, -): +def swap_plain(factory, dai, usdc, bob, amm_implementation_plain, zero_address): with boa.env.prank(bob): pool = factory.deploy_plain_pool( "test", "test", - [dai, usdc, ZERO_ADDRESS, ZERO_ADDRESS], + [dai, usdc, zero_address, zero_address], 2000, 1000000, 866, [bytes(b"")] * 4, - [ZERO_ADDRESS] * 4, + [zero_address] * 4, 0, 0, ) @@ -35,13 +27,7 @@ def swap_plain( @pytest.fixture(scope="module") -def swap_eth_rebasing( - factory, - weth, - steth, - charlie, - amm_implementation_plain, -): +def swap_eth_rebasing(factory, weth, steth, charlie, amm_implementation_plain, zero_address): # TODO: make steth rebasing @@ -55,7 +41,7 @@ def swap_eth_rebasing( 3000000, 866, [b""] * 4, - [ZERO_ADDRESS] * 4, + [zero_address] * 4, 0, 0, ) @@ -64,13 +50,7 @@ def swap_eth_rebasing( @pytest.fixture(scope="module") -def swap_oracle( - factory, - oracle_token_a, - oracle_token_b, - charlie, - amm_implementation_plain, -): +def swap_oracle(factory, oracle_token_a, oracle_token_b, charlie, amm_implementation_plain, zero_address): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") with boa.env.prank(charlie): @@ -83,7 +63,7 @@ def swap_oracle( 4000000, 866, [oracle_method_id, oracle_method_id, b"", b""], - [ZERO_ADDRESS] * 4, + [zero_address] * 4, 0, 0, ) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 8a3bad0d..0e0f4430 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -11,7 +11,7 @@ def weth(deployer): @pytest.fixture(scope="module") def usdc(deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", 6) + return boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", 6) @pytest.fixture(scope="module") @@ -44,9 +44,9 @@ def oracle_token_b(deployer): with boa.env.prank(deployer): return boa.load( "contracts/mocks/ERC20Oracle.vy", - "OTB", - "OTB", - 8, + "toETH", + "toETH", + 18, 1007580460035000000, ) diff --git a/tests/unitary/factory/test_factory.py b/tests/unitary/factory/test_factory.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unitary/pool/plain/conftest.py b/tests/unitary/pool/plain/conftest.py deleted file mode 100644 index c1939e00..00000000 --- a/tests/unitary/pool/plain/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest - - -@pytest.fixture(scope="module") -def coins(swap_plain): - return [swap_plain.coins(0), swap_plain.coins(1)] - - -@pytest.fixture(scope="module") -def decimals(coins): - return [coins[0].decimals(), coins[1].decimals()] diff --git a/tests/unitary/pool/plain/test_add_liquidity_initial.py b/tests/unitary/pool/plain/test_add_liquidity_initial.py deleted file mode 100644 index 8662f821..00000000 --- a/tests/unitary/pool/plain/test_add_liquidity_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -import boa -import pytest - -from tests.utils.tokens import mint_for_testing - - -@pytest.mark.parametrize("min_amount", [0, 10**18]) -def test_initial(alice, swap_plain, coins, decimals, min_amount, initial_amounts): - - amounts = [10**d for d in decimals] - for idx in range(2): - mint_for_testing(coins[idx], alice, amounts[idx], mint_eth=False) - - with boa.env.prank(alice): - swap_plain.add_liquidity( - amounts, - min_amount, - ) - - for coin, amount, initial in zip(coins, amounts, initial_amounts): - - assert coin.balanceOf(alice) == initial - amount - assert coin.balanceOf(swap_plain) == amount - - -@pytest.mark.parametrize("idx", range(2)) -def test_initial_liquidity_missing_coin(alice, swap_plain, idx, decimals): - amounts = [10**i for i in decimals] - amounts[idx] = 0 - with boa.reverts(), boa.env.prank(alice): - swap_plain.add_liquidity(amounts, 0) diff --git a/tests/utils/tokens.py b/tests/utils/tokens.py index 34633a90..237c3939 100644 --- a/tests/utils/tokens.py +++ b/tests/utils/tokens.py @@ -1,17 +1,29 @@ import boa +from boa.vyper.contract import VyperContract from eth_utils import to_checksum_address -def mint_for_testing(token_contract, addr, amount, mint_eth=False): +def mint_for_testing( + user: str, amount, token_contract: VyperContract | None, mint_eth: bool = False, is_rebase: bool = False +) -> None: + assert token_contract is not None or mint_eth - addr = to_checksum_address(addr) + user = to_checksum_address(user) - if token_contract.symbol() == "WETH": - boa.env.set_balance(addr, boa.env.get_balance(addr) + amount) - if not mint_eth: - with boa.env.prank(addr): - token_contract.deposit(value=amount) + if mint_eth: + boa.env.set_balance(user, boa.env.get_balance(user) + amount) else: - token_contract.eval(f"self.totalSupply += {amount}") - token_contract.eval(f"self.balanceOf[{addr}] += {amount}") - token_contract.eval(f"log Transfer(empty(address), {addr}, {amount})") + if token_contract.symbol() == "WETH": + boa.env.set_balance(user, boa.env.get_balance(user) + amount) + with boa.env.prank(user): + token_contract.deposit(value=amount) + elif not is_rebase: + token_contract.eval(f"self.totalSupply += {amount}") + token_contract.eval(f"self.balanceOf[{user}] += {amount}") + token_contract.eval(f"log Transfer(empty(address), {user}, {amount})") + else: + token_contract.eval(f"self.totalCoin += {amount}") + token_contract.eval(f"self.totalShares += self._get_shares_by_coins({amount})") + token_contract.eval(f"self.shares[{user}] += self._get_shares_by_coins({amount})") + token_contract.eval(f"log Transfer(empty(address), {user}, {amount})") + token_contract.eval(f"log TransferShares(empty(address), {user}, self._get_shares_by_coins({amount}))") From 0156a2d8631fa51bb970ec2c2088125fa7d86f64 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 27 Jun 2023 10:12:44 +0200 Subject: [PATCH 024/337] add auto rebase to mock --- contracts/mocks/ERC20Rebase.vy | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/contracts/mocks/ERC20Rebase.vy b/contracts/mocks/ERC20Rebase.vy index eef51920..32a024da 100644 --- a/contracts/mocks/ERC20Rebase.vy +++ b/contracts/mocks/ERC20Rebase.vy @@ -1,7 +1,7 @@ # @version ^0.3.7 """ -@notice Rebase ERC20 mock +@notice Rebase ERC20 mock with pseudo-randomly rebase by 1% on every transfer @dev This is for testing only, it is NOT safe for use @dev Based on stEth implementation """ @@ -34,13 +34,15 @@ allowances: HashMap[address, HashMap[address, uint256]] totalCoin: public(uint256) totalShares: public(uint256) shares: public(HashMap[address, uint256]) +IS_UP: immutable(bool) @external -def __init__(_name: String[64], _symbol: String[32], _decimals: uint256): +def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, is_up: bool): self.name = _name self.symbol = _symbol self.decimals = _decimals + IS_UP = is_up @external @@ -64,6 +66,7 @@ def allowance(_owner: address, _spender: address) -> uint256: @external def transfer(_to: address, _value: uint256) -> bool: + self._rebase() _shares: uint256 = self._get_shares_by_coins(_value) self.shares[msg.sender] -= _shares @@ -75,6 +78,7 @@ def transfer(_to: address, _value: uint256) -> bool: @external def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + self._rebase() _shares: uint256 = self._get_shares_by_coins(_value) self.shares[_from] -= _shares @@ -119,6 +123,14 @@ def share_price() -> uint256: return self._share_price() +@internal +def _rebase(): + if IS_UP: + self.totalCoin = self.totalCoin * 101 / 100 + else: + self.totalCoin = self.totalCoin * 99 / 100 + + @external def set_total_coin(total_coin: uint256) -> bool: assert self.totalShares != 0, "no shares" From 47918299228409f250e0e5d68f469362971cb560 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 27 Jun 2023 16:43:15 +0200 Subject: [PATCH 025/337] wip --- contracts/main/CurveStableSwapMetaNG.vy | 803 ++++++++++++++++-------- 1 file changed, 541 insertions(+), 262 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 8b82528a..532410f8 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,21 +1,37 @@ # @version 0.3.9 -# TODO: convert this to Meta implementation!!!!! """ -@title CurveStableSwapMetaNG +@title CurveStableSwap2NG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice 2 coin pool implementation with no lending, i.e. tokens are not - deposited into lending markets +@notice 2 coin pool implementation with no rehypothecation, i.e. tokens are not + deposited into other contracts. Supports only token pairs that are + similarly priced (or the underlying is similarly priced). @dev ERC20 support for return True/revert, return True/False, return None ERC20 tokens can have arbitrary decimals (<=18). Additional features include: - 1. Support for positive-rebasing and fee-on-transfer tokens + 1. Support for rebasing tokens: but this disables + `exchange_with_rebase` and `add_liquidity_with_rebase` 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + Note: Oracle precision _must_ be 10**18. 3. Support for ETH/WETH transfers - 4. Adds oracles for coin[1] w.r.t coin[0] + 4. Adds oracles based on AMM State Price (and _not_ last traded price). 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) + 6. Adds feature: `exchange_with_rebase`, which is inspired + by Uniswap V2: swaps that expect an ERC20 transfer to have occurred + prior to executing the swap. + Note: a. If pool contains rebasing tokens and `IS_REBASING` is True + then calling `exchange_with_rebase` will REVERT. + b. If pool contains rebasing token and `IS_REBASING` is False + then this is an incorrect implementation and rebases can be + stolen. + 7. Adds feature: `add_liquidity_with_rebase`. This is a version of + `add_liquidity` with optimistic ERC20 token transfers. As with + `exchange_with_rebase`, `IS_REBASING = True` disables this method. + 8. Adds `get_dx`: Similar to `get_dy` which returns an expected output + of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected + input of coin[i] for an output amount of coin[j]. """ from vyper.interfaces import ERC20 @@ -36,6 +52,29 @@ interface WETH: def deposit(): payable def withdraw(_amount: uint256): nonpayable +interface Curve: + def coins(i: uint256) -> address: view + def get_virtual_price() -> uint256: view + def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view + def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view + def fee() -> uint256: view + def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view + def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable + def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable + +interface Curve2: + def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable + +interface Curve3: + def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view + def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable + +interface Curve4: + def calc_token_amount(amounts: uint256[4], deposit: bool) -> uint256: view + def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable + # --------------------------------- Events ----------------------------------- event Transfer: @@ -99,16 +138,23 @@ event ApplyNewFee: fee: uint256 -# ---------------------------- Pool Parameters ------------------------------- +# ---------------------------- Pool Variables -------------------------------- WETH20: public(immutable(address)) -factory: public(address) - -coins: public(address[N_COINS]) N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 +IS_REBASING: immutable(bool) + +BASE_POOL: immutable(address) +BASE_N_COINS: immutable(uint256) +BASE_N_COINS_128: immutable(int128) +BASE_COINS: immutable(address[4]) + +factory: public(address) +coins: public(address[N_COINS]) +stored_balances: uint256[N_COINS] # ---------------------- Pool Amplification Parameters ----------------------- @@ -140,7 +186,7 @@ admin_balances: public(uint256[N_COINS]) # ----------------------- Oracle Specific vars ------------------------------- -rate_multiplier: uint256[N_COINS] +rate_multipliers: uint256 # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: uint256 @@ -174,13 +220,6 @@ CACHED_CHAIN_ID: immutable(uint256) salt: public(immutable(bytes32)) CACHED_DOMAIN_SEPARATOR: immutable(bytes32) -# ----------------------- Base Pool Specific vars ---------------------------- - -BASE_POOL: immutable(address) -BASE_LP_TOKEN: immutable(address) -BASE_COINS: immutable(address[4]) -BASE_N_COINS: immutable(int128) - # ------------------------------ AMM Setup ----------------------------------- @@ -197,16 +236,16 @@ def __init__( _ma_exp_time: uint256, _method_id: bytes4, _oracle: address, + _is_rebasing: bool, _base_pool: address, _base_lp_token: address, - _base_pool_coins: address[4], + _base_coins: address[4], ): """ @notice Initialize the pool contract - @param _name Name of the new plain pool - @param _symbol Symbol for the new plain pool - will be - concatenated with factory symbol - @param _coin Addresses of the coin paired against the base pool's lp token. + @param _name Name of the new plain pool. + @param _symbol Symbol for the new plain pool. + @param _coins List of addresses of the coins being used in the pool. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. Suggested values include: @@ -218,26 +257,40 @@ def __init__( 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 - @param _method_id First four bytes of the Keccak-256 hash of the function signature - of the oracle addresse that gives returns token rate. - Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] - @param _oracles Rate oracle addresse. + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + @param _is_rebasing If any of the coins rebases, then this should be set to True. + @param _base_pool Address of the base pool. + @param _base_lp_token Address of the basepool's lp token. + @param _base_coins Addresses of coins in the base pool. """ WETH20 = _weth - BASE_POOL = _base_pool - BASE_LP_TOKEN = _base_lp_token + IS_REBASING = _is_rebasing - name = concat("Curve.fi Factory Meta Pool: ", _name) - symbol = concat(_symbol, "-f") + name = _name + symbol = _symbol - self.coins = [_coin, BASE_LP_TOKEN] - self.rate_multiplier = _rate_multiplier + self.coins = [_coin, _base_lp_token] + self.rate_multipliers = _rate_multiplier self.oracles = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) - # TODO: initialise up base coins and base n coins here - for coin in BASE_COINS: - ERC20(coin).approve(BASE_POOL, MAX_UINT256) + BASE_COINS = _base_coins + BASE_POOL = _base_pool + + base_n_coins: uint256 = 0 + for coin in _base_coins: + + if coin == empty(address): + break + + ERC20(coin).approve(BASE_POOL, max_value(uint256)) + base_n_coins += 1 + + BASE_N_COINS = base_n_coins + BASE_N_COINS_128 = convert(BASE_N_COINS, int128) A: uint256 = _A * A_PRECISION self.initial_A = A @@ -250,6 +303,7 @@ def __init__( self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: check if this line is correct self.ma_last_time = block.timestamp + # EIP712 NAME_HASH = keccak256(name) salt = block.prevhash CACHED_CHAIN_ID = chain.id @@ -268,20 +322,9 @@ def __init__( log Transfer(empty(address), self, 0) -@external -def set_ma_exp_time(_ma_exp_time: uint256): - """ - @notice Set the moving average window of the price oracle. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) - """ - assert msg.sender == Factory(self.factory).admin() # dev: only owner - assert _ma_exp_time != 0 - - self.ma_exp_time = _ma_exp_time - - # ------------------ Token transfers in and out of the AMM ------------------- + @payable @external def __default__(): @@ -396,6 +439,82 @@ def _transfer_out( ) +# -------------------------- AMM Special Methods ----------------------------- + + +@view +@internal +def _stored_rates() -> uint256[N_COINS]: + """ + @notice Gets rate multipliers for each coin. + @dev If the coin has a rate oracle that has been properly initialised, + this method queries that rate by static-calling an external + contract. + """ + + rates: uint256[N_COINS] = [ + self.rate_multipliers, + Curve(BASE_POOL).get_virtual_price() + ] + oracles: uint256 = self.oracles + + if oracles == 0: + return rates + + # NOTE: assumed that response is of precision 10**18 + response: Bytes[32] = raw_call( + convert(oracles % 2**160, address), + _abi_encode(oracles & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ) + + assert len(response) != 0 + rates[0] = rates[0] * convert(response, uint256) / PRECISION + + return rates + + +@view +@internal +def _balances() -> uint256[N_COINS]: + """ + @notice Calculates the pool's balances _excluding_ the admin's balances. + @dev This method ensures LPs keep all rebases and admin only claims swap fees. + """ + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + + return result + + +@internal +def _increase_balances(balances: uint256[N_COINS]): + """ + @notice Increases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer into the pool. + """ + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] += balances[i] + self.stored_balances = stored_balances + + +@internal +def _decrease_balances(balances: uint256[N_COINS]): + """ + @notice Decreases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer out of the pool. + """ + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] -= balances[i] + self.stored_balances = stored_balances + + # -------------------------- AMM Main Functions ------------------------------ @@ -431,7 +550,8 @@ def exchange( _use_eth, _receiver, empty(address), - empty(bytes32) + empty(bytes32), + False ) @@ -448,9 +568,10 @@ def exchange_extended( _cb: bytes32 ) -> uint256: """ - @notice Perform an exchange between two coins + @notice Perform an exchange between two coins after a callback @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth) + Not payable (does not accept eth). Users of this method are dex aggregators, + arbitrageurs, or other users who do not wish to grant approvals to the contract. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -468,10 +589,60 @@ def exchange_extended( _use_eth, _receiver, msg.sender, # <---------------------------- callbacker is msg.sender. - _cb + _cb, + False ) +@external +@nonreentrant('lock') +def exchange_with_rebase( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins without transferring token in + @dev The contract swaps tokens based on a change in balance of coin[i]. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `exchange_on_rebase`. + The method is non-payable: does not accept native token. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._exchange( + msg.sender, + 0, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32), + True, # <--------------------------------------- swap optimistically. + ) + + +@payable +@external +@nonreentrant('lock') +def exchange_underlying() -> uint256: + # TODO: Needs exchange_underlying + # TODO: Do we need exchange_underlying with callbacks? with rebase (optimistic swap)? + pass + + @payable @external @nonreentrant('lock') @@ -488,98 +659,48 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - coins: address[N_COINS] = self.coins - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - - for i in range(N_COINS): - - if _amounts[i] > 0: - - if coins[i] == WETH20: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - msg.value, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - _use_eth - ) - - else: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - False - ) - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - - if total_supply > 0: - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) - - else: - - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[_receiver] += mint_amount - self.totalSupply = total_supply - log Transfer(empty(address), _receiver, mint_amount) + return self._add_liquidity( + msg.sender, + _amounts, + _min_mint_amount, + _use_eth, + _receiver, + msg.value, + False, # <--------------------- Does not expect optimistic transfers. + ) - log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) - return mint_amount +@external +@nonreentrant('lock') +def add_liquidity_with_rebase( + _amounts: uint256[N_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @dev The contract adds liquidity based on a change in balance of coins. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `add_liquidity_on_rebase`. + The method is non-payable: does not accept native token. + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._add_liquidity( + msg.sender, + _amounts, + _min_mint_amount, + _use_eth, + _receiver, + 0, + True, # <------------------------------ Expects optimistic transfers. + ) @external @@ -611,6 +732,9 @@ def remove_liquidity_one_coin( self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + # Decrease coin[i] balance in self.stored_balances + self.stored_balances[i] -= dy[0] + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) self.save_p_from_price(dy[2]) @@ -641,11 +765,12 @@ def remove_liquidity_imbalance( new_balances: uint256[N_COINS] = old_balances for i in range(N_COINS): - amount: uint256 = _amounts[i] - if amount != 0: - new_balances[i] -= amount + if _amounts[i] != 0: + new_balances[i] -= _amounts[i] + self._transfer_out(coins[i], _amounts[i], _use_eth, _receiver) - self._transfer_out(coins[i], amount, _use_eth, _receiver) + # Decrease balances in self.stored_balances + self._decrease_balances(_amounts) D1: uint256 = self.get_D_mem(rates, new_balances, amp) @@ -707,9 +832,11 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(coins[i], value, _use_eth, _receiver) + # Decrease balances in self.stored_balances + self._decrease_balances(amounts) + total_supply -= _burn_amount self.balanceOf[msg.sender] -= _burn_amount self.totalSupply = total_supply @@ -746,7 +873,8 @@ def _exchange( use_eth: bool, receiver: address, callbacker: address, - callback_sig: bytes32 + callback_sig: bytes32, + expect_optimistic_transfer: bool = False ) -> uint256: assert i != j # dev: coin index out of range @@ -759,13 +887,27 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- - # `dx` that `_transfer_in` gives is whatever the pool received after ERC20 - # transfer - dx: uint256 = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth - ) + dx: uint256 = 0 + + if expect_optimistic_transfer: + + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] + + assert dx == _dx, "Pool did not receive tokens for swap" + + else: + + # `dx` is whatever the pool received after ERC20 transfer: + dx = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) + + # Update stored balances + self.stored_balances[i] += dx # ------------------------------------------------------------------------ @@ -796,6 +938,9 @@ def _exchange( self._transfer_out(coins[j], dy, use_eth, receiver) + # Update Stored Balances: + self.stored_balances[j] -= dy + # ------------------------------------------------------------------------ log TokenExchange(msg.sender, i, _dx, j, dy) @@ -803,112 +948,165 @@ def _exchange( return dy -@view @internal -def _stored_rates() -> uint256[N_COINS]: +def _add_liquidity( + sender: address, + amounts: uint256[N_COINS], + min_mint_amount: uint256, + use_eth: bool, + receiver: address, + mvalue: uint256, + expect_optimistic_transfer: bool = False, +) -> uint256: - rates: uint256[N_COINS] = self.rate_multipliers + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + coins: address[N_COINS] = self.coins - for i in range(N_COINS): - oracle: uint256 = self.oracles[i] - if oracle == 0: - continue + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) - # NOTE: assumed that response is of precision 10**18 - response: Bytes[32] = raw_call( - convert(oracle % 2**160, address), - _abi_encode(oracle & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ) + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances - assert len(response) != 0 - rates[1] = rates[1] * convert(response, uint256) / PRECISION + # -------------------------- Do Transfers In ----------------------------- - return rates + if expect_optimistic_transfer: + dx: uint256 = 0 -@view -@internal -def _balances() -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] - return result + for i in range(N_COINS): + if amounts[i] > 0: -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" - else: # when t1 == 0 or block.timestamp >= t1 - return A1 + new_balances[i] += dx + else: -@pure -@internal -def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = _rates[i] * _balances[i] / PRECISION - return result + assert total_supply != 0 # dev: initial deposit requires all coins + else: -@pure -@internal -def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively + for i in range(N_COINS): - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + if amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + mvalue, + empty(address), + empty(bytes32), + sender, + empty(address), + use_eth + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + sender, + empty(address), + False + ) - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - for x in _xp: - S += x - if S == 0: - return 0 + else: - D: uint256 = S - Ann: uint256 = _amp * N_COINS - for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS - Dprev: uint256 = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) - # Equality with the precision of 1 - if D > Dprev: - if D - Dprev <= 1: - return D - else: - if Dprev - D <= 1: - return D - # convergence typically occurs in 4 rounds or less, this should be unreachable! - # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` - raise + assert total_supply != 0 # dev: initial deposit requires all coins + + # Add incoming balance + self._increase_balances(new_balances) + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), receiver, mint_amount) + + log AddLiquidity(sender, amounts, fees, D1, total_supply) + + return mint_amount -@view @internal -def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) - return self.get_D(xp, _amp) +def _withdraw_admin_fees(): + + receiver: address = Factory(self.factory).get_fee_receiver(self) + amounts: uint256[N_COINS] = self.admin_balances + + for i in range(N_COINS): + + if amounts[i] > 0: + + if self.coins[i] == WETH20: + raw_call(receiver, b"", value=amounts[i]) + else: + assert ERC20(self.coins[i]).transfer( + receiver, + amounts[i], + default_return_value=True + ) + + self.admin_balances = empty(uint256[N_COINS]) + # Reduce stored balances: + self._decrease_balances(amounts) + + +# --------------------------- AMM Math Functions ----------------------------- @view @@ -971,6 +1169,42 @@ def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, raise +@pure +@internal +def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for i in range(255): + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + Dprev: uint256 = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + @pure @internal def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: @@ -1019,6 +1253,44 @@ def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: raise +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@pure +@internal +def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: + result: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + result[i] = _rates[i] * _balances[i] / PRECISION + return result + + +@view +@internal +def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: + xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + @view @internal def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: @@ -1058,27 +1330,6 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: return [dy, dy_0 - dy, last_p] -@internal -def _withdraw_admin_fees(): - receiver: address = Factory(self.factory).get_fee_receiver(self) - - for i in range(N_COINS): - amount: uint256 = self.admin_balances[i] - - if amount > 0: - - if self.coins[i] == WETH20: - raw_call(receiver, b"", value=amount) - else: - assert ERC20(self.coins[i]).transfer( - receiver, - amount, - default_return_value=True - ) - - self.admin_balances = empty(uint256[N_COINS]) - - # -------------------------- AMM Price Methods ------------------------------- @@ -1366,7 +1617,9 @@ def ema_price() -> uint256: @view def get_p() -> uint256: amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + xp: uint256[N_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) D: uint256 = self.get_D(xp, amp) return self._get_p(xp, amp, D) @@ -1397,6 +1650,13 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: return (x - xp[i]) * PRECISION / rates[i] +@view +@external +def get_dx_underlying(i: int128, j: int128, dx: uint256) -> uint256: + # TODO: Needs get_dx_underlying + pass + + @view @external def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @@ -1418,6 +1678,13 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: return (dy - fee) * PRECISION / rates[j] +@view +@external +def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: + # TODO: Needs get_dy_underlying + pass + + @view @external def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: @@ -1542,7 +1809,7 @@ def get_balances() -> uint256[N_COINS]: @view @external def oracle(_idx: uint256) -> address: - return convert(self.oracle % 2**160, address) + return convert(self.oracles % 2**160, address) # --------------------------- AMM Admin Functions ---------------------------- @@ -1606,3 +1873,15 @@ def apply_new_fee(): self.fee = fee self.admin_action_deadline = 0 log ApplyNewFee(fee) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert _ma_exp_time != 0 + + self.ma_exp_time = _ma_exp_time From 7c776d2e59ee4000de47b803f0ff0abc9b69096b Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 27 Jun 2023 20:06:18 +0200 Subject: [PATCH 026/337] add fixtures --- .github/workflows/{tests.yaml => gauge.yaml} | 16 +- contracts/mocks/ERC20.vy | 11 +- contracts/mocks/ERC20Oracle.vy | 11 +- .../{ERC20Rebase.vy => ERC20Rebasing.vy} | 18 ++- tests/conftest.py | 144 +++++++++++++++++- tests/fixtures/__init__.py | 0 tests/fixtures/accounts.py | 47 ++++++ tests/fixtures/constants.py | 10 +- tests/fixtures/factory.py | 28 +++- tests/fixtures/pools.py | 120 +++++++-------- tests/fixtures/tokens.py | 96 ++++++++---- tests/gauge/__init__.py | 0 tests/gauge/test_rewards.py | 39 +++++ tests/pools/__init__.py | 0 tests/pools/basic/__init__.py | 0 tests/pools/meta/__init__.py | 0 tests/utils/__init__.py | 0 tests/utils/tokens.py | 15 +- 18 files changed, 427 insertions(+), 128 deletions(-) rename .github/workflows/{tests.yaml => gauge.yaml} (63%) rename contracts/mocks/{ERC20Rebase.vy => ERC20Rebasing.vy} (87%) create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/gauge/__init__.py create mode 100644 tests/gauge/test_rewards.py create mode 100644 tests/pools/__init__.py create mode 100644 tests/pools/basic/__init__.py create mode 100644 tests/pools/meta/__init__.py create mode 100644 tests/utils/__init__.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/gauge.yaml similarity index 63% rename from .github/workflows/tests.yaml rename to .github/workflows/gauge.yaml index 96610ce5..e7e6507a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/gauge.yaml @@ -1,10 +1,18 @@ -# TODO: split actions later -name: tests +name: gauge -on: ["push", "pull_request"] +on: + pull_request: + paths: + - "tests/gauge/*.py" + - "contracts/LiquidityGauge.vy" + push: + paths: + - "tests/gauge/*.py" + - "contracts/LiquidityGauge.vy" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEB3_PROVIDER_URL: https://eth-mainnet.g.alchemy.com/v2/IR9-_e7eMsqvFsvGIjC4BimpaamH70XL jobs: tests: @@ -33,4 +41,4 @@ jobs: - name: Run Tests run: | source .venv/bin/activate - pytest tests -n auto + pytest tests/gauge/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto diff --git a/contracts/mocks/ERC20.vy b/contracts/mocks/ERC20.vy index 81f9c254..fc1156e5 100644 --- a/contracts/mocks/ERC20.vy +++ b/contracts/mocks/ERC20.vy @@ -1,4 +1,4 @@ -# @version ^0.3.7 +# @version ^0.3.9 """ @notice Mock ERC20 for testing @@ -60,3 +60,12 @@ def approve(_spender: address, _value: uint256) -> bool: self.allowances[msg.sender][_spender] = _value log Approval(msg.sender, _spender, _value) return True + + +@external +def _mint_for_testing(_target: address, _value: uint256) -> bool: + self.totalSupply += _value + self.balanceOf[_target] += _value + log Transfer(empty(address), _target, _value) + + return True diff --git a/contracts/mocks/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy index eee88a7b..dcef58d6 100644 --- a/contracts/mocks/ERC20Oracle.vy +++ b/contracts/mocks/ERC20Oracle.vy @@ -1,4 +1,4 @@ -# @version ^0.3.7 +# @version ^0.3.9 """ @notice Mock ERC20 with oracle @@ -85,3 +85,12 @@ def exchangeRate() -> uint256: def set_exchange_rate(rate: uint256) -> bool: self.exchange_rate = rate return True + + +@external +def _mint_for_testing(_target: address, _value: uint256) -> bool: + self.totalSupply += _value + self.balanceOf[_target] += _value + log Transfer(empty(address), _target, _value) + + return True diff --git a/contracts/mocks/ERC20Rebase.vy b/contracts/mocks/ERC20Rebasing.vy similarity index 87% rename from contracts/mocks/ERC20Rebase.vy rename to contracts/mocks/ERC20Rebasing.vy index 32a024da..99a35255 100644 --- a/contracts/mocks/ERC20Rebase.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -1,7 +1,7 @@ -# @version ^0.3.7 +# @version ^0.3.9 """ -@notice Rebase ERC20 mock with pseudo-randomly rebase by 1% on every transfer +@notice Rebasing ERC20 mock with rebase by 1% on every transfer @dev This is for testing only, it is NOT safe for use @dev Based on stEth implementation """ @@ -137,3 +137,17 @@ def set_total_coin(total_coin: uint256) -> bool: self.totalCoin = total_coin return True + + +@external +def _mint_for_testing(_target: address, _value: uint256) -> bool: + self.totalCoin += _value + + _shares: uint256 = self._get_shares_by_coins(_value) + self.totalShares += _shares + self.shares[_target] += _shares + + log Transfer(empty(address), _target, _value) + log TransferShares(empty(address), _target, _shares) + + return True diff --git a/tests/conftest.py b/tests/conftest.py index 73ed2fda..26a57c5a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,146 @@ +import os +from itertools import combinations + +import boa +import pytest + pytest_plugins = [ "tests.fixtures.accounts", - "tests.fixtures.tokens", - "tests.fixtures.pools", + "tests.fixtures.constants", "tests.fixtures.factory", + "tests.fixtures.pools", + "tests.fixtures.tokens", ] + +pool_types = {"basic": 0, "meta": 1} +token_types = {"plain": 0, "eth": 1, "oracle": 2, "rebasing": 3} +return_types = {"revert": 0, "False": 1, "None": 2} + + +def pytest_addoption(parser): + parser.addoption( + "--pool-size", + action="store", + default="2", + help="comma-separated list of plain pool sizes to test against", + ) + # TODO: add meta implementation + parser.addoption( + "--pool-type", + action="store", + default="basic", + help="comma-separated list of pool types to test against", + ) + parser.addoption( + "--token-types", + action="store", + default="plain,eth,oracle,rebasing", + help="comma-separated list of ERC20 token types to test against", + ) + parser.addoption( + "--decimals", + action="store", + default="18,18", + help="comma-separated list of ERC20 token precisions to test against", + ) + parser.addoption( + "--return-type", + action="store", + default="revert,False,None", + help="comma-separated list of ERC20 token return types to test against", + ) + + +def pytest_generate_tests(metafunc): + if "pool_size" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("pool_size").split(",") + pool_sizes = [int(v) for v in cli_options] + + # TODO: remove after adding implementations + assert pool_sizes == [2], "Only 2-coin pools supported" + + metafunc.parametrize( + "pool_size", + pool_sizes, + indirect=True, + ids=[f"(PoolSize={i})" for i in cli_options], + ) + + if "pool_type" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("pool_type").split(",") + pool_type_ids = [pool_types[v] for v in cli_options] + metafunc.parametrize( + "pool_type", + pool_type_ids, + indirect=True, + ids=[f"(PoolType={i})" for i in cli_options], + ) + + if "pool_token_types" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("token_types").split(",") + + # TODO: only 2-coin pools are supported + combs = list(combinations(cli_options, 2)) + # do not include (eth,eth) pair + for t in cli_options: + if t != "eth": + combs.append((t, t)) + + metafunc.parametrize( + "pool_token_types", + [(token_types[v[0]], token_types[v[1]]) for v in combs], + indirect=True, + ids=[f"(PoolTokenTypes={c})" for c in combs], + ) + + if "decimals" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("decimals") + metafunc.parametrize( + "decimals", + [[int(i) for i in cli_options.split(",")]], + indirect=True, + ids=[f"(Decimals={cli_options})"], + ) + + if "return_type" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("return_type").split(",") + return_type_ids = [return_types[v] for v in cli_options] + + metafunc.parametrize( + "return_type", + return_type_ids, + indirect=True, + ids=[f"(ReturnType={i})" for i in cli_options], + ) + + +@pytest.fixture(scope="session") +def pool_size(request): + return request.param + + +@pytest.fixture(scope="session") +def pool_type(request): + return request.param + + +@pytest.fixture(scope="session") +def pool_token_types(request): + return request.param + + +@pytest.fixture(scope="session") +def return_type(request): + return request.param + + +@pytest.fixture(scope="session") +def decimals(request): + return request.param + + +@pytest.fixture(scope="module") +def forked_chain(): + rpc_url = os.getenv("WEB3_PROVIDER_URL") + assert rpc_url is not None, "Provider url is not set, add WEB3_PROVIDER_URL param to env" + boa.env.fork(url=rpc_url) diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 3b622c00..27bc4f36 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -3,6 +3,8 @@ from boa.environment import AddressType from eth_account.account import Account, LocalAccount +from tests.utils.tokens import mint_for_testing + @pytest.fixture(scope="module") def deployer() -> AddressType: @@ -27,3 +29,48 @@ def fee_receiver() -> AddressType: @pytest.fixture(scope="module") def eth_acc() -> LocalAccount: return Account.create() + + +@pytest.fixture(scope="module") +def alice(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def bob(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def charlie(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def dave(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def erin(): + return boa.env.generate_address() + + +@pytest.fixture(scope="module") +def frank(): + return boa.env.generate_address() + + +# <--------------------- Functions ---------------------> +@pytest.fixture(scope="module") +def mint_owner(owner, pool_tokens, initial_amounts): + mint_for_testing(owner, 10**18, None, True) + for pool_token, amount in zip(pool_tokens, initial_amounts): + mint_for_testing(owner, amount, pool_token, False) + + +@pytest.fixture(scope="module") +def approve_owner(owner, pool_tokens, swap): + for pool_token in pool_tokens: + with boa.env.prank(owner): + pool_token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index a51f3cf8..62c0b27b 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -3,12 +3,12 @@ INITIAL_AMOUNT = 1_000_000 -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def initial_amounts(decimals: list[int]) -> list[int]: return [INITIAL_AMOUNT * 10**precision for precision in decimals] -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def initial_amounts_underlying(underlying_decimals: list[int]) -> list[int]: amts = [INITIAL_AMOUNT * 10**precision for precision in underlying_decimals] for i in range(1, len(underlying_decimals)): @@ -16,11 +16,11 @@ def initial_amounts_underlying(underlying_decimals: list[int]) -> list[int]: return amts -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def deposit_amounts(decimals: list[int]) -> list[int]: - return [INITIAL_AMOUNT * 10**precision for precision in decimals] + return [INITIAL_AMOUNT // 2 * 10**18 for _ in decimals] # xp is always 18 -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def zero_address() -> str: return "0x0000000000000000000000000000000000000000" diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 64db941e..1b2a685b 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -14,8 +14,9 @@ def gauge_implementation(deployer, gauge_interface): @pytest.fixture(scope="module") -def amm_interface_plain(): - return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") +def amm_interface_plain(pool_size): + if pool_size == 2: + return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") @pytest.fixture(scope="module") @@ -51,10 +52,25 @@ def factory( owner, weth, ) + return _factory + +# <--------------------- Functions ---------------------> +# TODO: add Factory Meta Implementation +@pytest.fixture(scope="module") +def set_plain_implementations(owner, factory, pool_size, pool_type, amm_implementation_plain): with boa.env.prank(owner): - _factory.set_plain_implementations(2, 0, amm_implementation_plain) - _factory.set_gauge_implementation(gauge_implementation) - # TODO: add Factory Meta Implementation + factory.set_plain_implementations(pool_size, pool_type, amm_implementation_plain.address) - return _factory + +@pytest.fixture(scope="module") +def set_gauge_implementation(owner, factory, gauge_implementation): + with boa.env.prank(owner): + factory.set_gauge_implementation(gauge_implementation.address) + + +@pytest.fixture(scope="module") +def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): + with boa.env.prank(owner): + gauge_address = factory.deploy_gauge(swap.address) + return gauge_interface.at(gauge_address) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index c5fd992d..4b298746 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,82 +2,72 @@ import pytest from eth_utils import function_signature_to_4byte_selector -# TODO: rebasing pool, meta pool +# TODO: meta pool @pytest.fixture(scope="module") -def swap_plain(factory, dai, usdc, bob, amm_implementation_plain, zero_address): - - with boa.env.prank(bob): - - pool = factory.deploy_plain_pool( - "test", - "test", - [dai, usdc, zero_address, zero_address], - 2000, - 1000000, - 866, - [bytes(b"")] * 4, - [zero_address] * 4, - 0, - 0, - ) - - return amm_implementation_plain.at(pool) - - -@pytest.fixture(scope="module") -def swap_eth_rebasing(factory, weth, steth, charlie, amm_implementation_plain, zero_address): - - # TODO: make steth rebasing - - with boa.env.prank(charlie): - - pool = factory.deploy_plain_pool( - "test", - "test", - [weth, steth], - 1000, - 3000000, - 866, - [b""] * 4, - [zero_address] * 4, - 0, - 0, - ) - - return amm_implementation_plain.at(pool) - - -@pytest.fixture(scope="module") -def swap_oracle(factory, oracle_token_a, oracle_token_b, charlie, amm_implementation_plain, zero_address): +def swap( + alice, + mint_owner, + factory, + pool_token_types, + pool_tokens, + amm_interface_plain, + amm_implementation_plain, + set_plain_implementations, + zero_address, +): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") - with boa.env.prank(charlie): - + A = 2000 + fee = 1000000 + method_ids = [bytes(b"")] * 4 + oracles = [zero_address] * 4 + asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + is_rebasing = False + + for i, t in enumerate(pool_token_types): + if t == 0: + A = 2000 + fee = 1000000 + asset_type = 0 + elif t == 1: + A = 1000 + fee = 3000000 + asset_type = 1 + elif t == 2: + A = 1000 + fee = 3000000 + asset_type = 1 + method_ids[i] = oracle_method_id + oracles[i] = pool_tokens[i].address + elif t == 3: + A = 500 + fee = 4000000 + asset_type = 1 + is_rebasing = True + + with boa.env.prank(alice): pool = factory.deploy_plain_pool( "test", "test", - [oracle_token_a, oracle_token_b], - 500, - 4000000, + [pool_tokens[0].address, pool_tokens[1].address, zero_address, zero_address], + A, + fee, 866, - [oracle_method_id, oracle_method_id, b"", b""], - [zero_address] * 4, - 0, + method_ids, + oracles, + asset_type, 0, + is_rebasing, ) - return amm_implementation_plain.at(pool) + return amm_interface_plain.at(pool) -@pytest.fixture(scope="module") -def swap_meta( - factory, - dai, - base_pool_token, # TODO: implement base pool token - charlie, - amm_implementation_meta, -): - # TODO: implement metapools - pass +# <--------------------- Functions ---------------------> +# TODO: add Factory Meta Implementation +@pytest.fixture +def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): + with boa.env.prank(owner): + swap.add_liquidity(deposit_amounts, 0) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 0e0f4430..627040cd 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -2,6 +2,15 @@ import pytest +@pytest.fixture(scope="module") +def plain_tokens(deployer, decimals): + tokens = [] + with boa.env.prank(deployer): + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", decimals[0])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", decimals[1])) + return tokens + + @pytest.fixture(scope="module") def weth(deployer): with boa.env.prank(deployer): @@ -9,53 +18,80 @@ def weth(deployer): @pytest.fixture(scope="module") -def usdc(deployer): +def oracle_tokens(deployer, decimals): + tokens = [] with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", 6) + tokens.append( + boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTA", + "OTA", + decimals[0], + 1006470359024000000, + ) + ) + tokens.append( + boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTB", + "OTB", + decimals[0], + 1007580460035000000, + ) + ) + return tokens @pytest.fixture(scope="module") -def dai(deployer): +def rebase_tokens(deployer, decimals): + tokens = [] with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", 18) + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "downETH", "downETH", decimals[0], False)) + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", decimals[0], True)) + return tokens @pytest.fixture(scope="module") -def steth(deployer): - with boa.env.prank(deployer): - # TODO: turn this into a rebasing implementation - return boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", 18) +def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens): + pool_tokens = [] + for i, t in enumerate(pool_token_types): + if t == 0: + pool_tokens.append(plain_tokens[i]) + elif t == 1: + pool_tokens.append(weth) + elif t == 2: + pool_tokens.append(oracle_tokens[i]) + elif t == 3: + pool_tokens.append(rebase_tokens[i]) + else: + raise ValueError("Wrong pool token type") + + return pool_tokens @pytest.fixture(scope="module") -def oracle_token_a(deployer): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20Oracle.vy", - "OTA", - "OTA", - 18, - 1006470359024000000, - ) +def coin_reward(owner): + with boa.env.prank(owner): + return boa.load("contracts/mocks/ERC20.vy", "CR", "CR", 18) @pytest.fixture(scope="module") -def oracle_token_b(deployer): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20Oracle.vy", - "toETH", - "toETH", - 18, - 1007580460035000000, - ) +def coin_reward_a(owner, mint_owner): + with boa.env.prank(owner): + return boa.load("contracts/mocks/ERC20.vy", "CRa", "CRa", 18) @pytest.fixture(scope="module") -def plain_coins(usdt, dai): - yield [usdt, dai] +def coin_reward_b(owner): + with boa.env.prank(owner): + return boa.load("contracts/mocks/ERC20.vy", "CRb", "CRb", 18) @pytest.fixture(scope="module") -def rebasing_coins(weth, steth): - yield [steth, weth] +def coin_rewards_additional(owner): + coins = [] + with boa.env.prank(owner): + for i in range(8): + coins.append(boa.load("contracts/mocks/ERC20.vy", f"CR{i}", f"CR{i}", 18)) + + return coins diff --git a/tests/gauge/__init__.py b/tests/gauge/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/gauge/test_rewards.py b/tests/gauge/test_rewards.py new file mode 100644 index 00000000..0568e9ba --- /dev/null +++ b/tests/gauge/test_rewards.py @@ -0,0 +1,39 @@ +import boa +import pytest + +REWARD = 10**20 +WEEK = 7 * 86400 +LP_AMOUNT = 10**18 + + +@pytest.mark.usefixtures("forked_chain") +class TestGaugeRewards: + class TestAddRewards: + @pytest.fixture(autouse=True) + def initial_setup(self, owner, gauge, swap, add_initial_liquidity, set_gauge_implementation): + with boa.env.prank(owner): + swap.approve(gauge.address, LP_AMOUNT) + gauge.deposit(LP_AMOUNT) + + def test_set_rewards_no_deposit(self, owner, coin_reward, swap, gauge, zero_address): + with boa.env.prank(owner): + gauge.add_reward(coin_reward.address, owner) + + assert swap.balanceOf(gauge.address) == LP_AMOUNT + assert gauge.reward_tokens(0) == coin_reward.address + assert gauge.reward_tokens(1) == zero_address + + def test_multiple_reward_tokens(self, owner, coin_reward, coin_reward_a, coin_reward_b, gauge): + coins = [coin_reward.address, coin_reward_a.address, coin_reward_b.address] + with boa.env.prank(owner): + for coin in coins: + gauge.add_reward(coin, owner) + + assert coins == [gauge.reward_tokens(i) for i in range(3)] + + def test_cant_exceed_max(self, owner, coin_rewards_additional, gauge): + with boa.env.prank(owner): + for i in range(8): + gauge.add_reward(coin_rewards_additional[i].address, owner) + with boa.reverts(): + gauge.add_reward(coin_rewards_additional[i].address, owner) diff --git a/tests/pools/__init__.py b/tests/pools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/basic/__init__.py b/tests/pools/basic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/meta/__init__.py b/tests/pools/meta/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/tokens.py b/tests/utils/tokens.py index 237c3939..46bc0c67 100644 --- a/tests/utils/tokens.py +++ b/tests/utils/tokens.py @@ -3,9 +3,7 @@ from eth_utils import to_checksum_address -def mint_for_testing( - user: str, amount, token_contract: VyperContract | None, mint_eth: bool = False, is_rebase: bool = False -) -> None: +def mint_for_testing(user: str, amount, token_contract: VyperContract | None, mint_eth: bool = False) -> None: assert token_contract is not None or mint_eth user = to_checksum_address(user) @@ -17,13 +15,6 @@ def mint_for_testing( boa.env.set_balance(user, boa.env.get_balance(user) + amount) with boa.env.prank(user): token_contract.deposit(value=amount) - elif not is_rebase: - token_contract.eval(f"self.totalSupply += {amount}") - token_contract.eval(f"self.balanceOf[{user}] += {amount}") - token_contract.eval(f"log Transfer(empty(address), {user}, {amount})") else: - token_contract.eval(f"self.totalCoin += {amount}") - token_contract.eval(f"self.totalShares += self._get_shares_by_coins({amount})") - token_contract.eval(f"self.shares[{user}] += self._get_shares_by_coins({amount})") - token_contract.eval(f"log Transfer(empty(address), {user}, {amount})") - token_contract.eval(f"log TransferShares(empty(address), {user}, self._get_shares_by_coins({amount}))") + with boa.env.prank(user): + token_contract._mint_for_testing(user, amount) From 2eb16a32838268cac57b23f0a134d2e6616c7889 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 27 Jun 2023 20:11:44 +0200 Subject: [PATCH 027/337] fix deployer --- tests/fixtures/pools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 4b298746..fa0e27a0 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -7,7 +7,7 @@ @pytest.fixture(scope="module") def swap( - alice, + owner, mint_owner, factory, pool_token_types, @@ -47,7 +47,7 @@ def swap( asset_type = 1 is_rebasing = True - with boa.env.prank(alice): + with boa.env.prank(owner): pool = factory.deploy_plain_pool( "test", "test", From 088e621174bf620579546459d7d6e24102dee4f9 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 27 Jun 2023 20:55:12 +0200 Subject: [PATCH 028/337] move token to secrets --- .github/workflows/gauge.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gauge.yaml b/.github/workflows/gauge.yaml index e7e6507a..b2f0e9fc 100644 --- a/.github/workflows/gauge.yaml +++ b/.github/workflows/gauge.yaml @@ -12,7 +12,7 @@ on: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WEB3_PROVIDER_URL: https://eth-mainnet.g.alchemy.com/v2/IR9-_e7eMsqvFsvGIjC4BimpaamH70XL + WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} jobs: tests: From 52baa97f3b39135362eddf26bef79e57b289aee9 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:04:57 +0200 Subject: [PATCH 029/337] wip: adding exchange extended... --- contracts/main/CurveStableSwap2NG.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 519 ++++++++++++------------ 2 files changed, 268 insertions(+), 253 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 43a509f6..7b8e58c3 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -122,7 +122,7 @@ WETH20: public(immutable(address)) N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(bool) +IS_REBASING: public(immutable(bool)) factory: public(address) coins: public(address[N_COINS]) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 532410f8..a64eaed0 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -9,29 +9,13 @@ @dev ERC20 support for return True/revert, return True/False, return None ERC20 tokens can have arbitrary decimals (<=18). Additional features include: - 1. Support for rebasing tokens: but this disables - `exchange_with_rebase` and `add_liquidity_with_rebase` - 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + 1. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. - 3. Support for ETH/WETH transfers - 4. Adds oracles based on AMM State Price (and _not_ last traded price). - 5. Adds exchanging tokens with callbacks that allows for: - a. reduced ERC20 token transfers in zap contracts - b. swaps without transferFrom (no need for token approvals) - 6. Adds feature: `exchange_with_rebase`, which is inspired - by Uniswap V2: swaps that expect an ERC20 transfer to have occurred - prior to executing the swap. - Note: a. If pool contains rebasing tokens and `IS_REBASING` is True - then calling `exchange_with_rebase` will REVERT. - b. If pool contains rebasing token and `IS_REBASING` is False - then this is an incorrect implementation and rebases can be - stolen. - 7. Adds feature: `add_liquidity_with_rebase`. This is a version of - `add_liquidity` with optimistic ERC20 token transfers. As with - `exchange_with_rebase`, `IS_REBASING = True` disables this method. - 8. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 2. Adds oracles based on AMM State Price (and _not_ last traded price). + 3. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. + 4. Adds `get_dx_underlying`. """ from vyper.interfaces import ERC20 @@ -55,12 +39,10 @@ interface WETH: interface Curve: def coins(i: uint256) -> address: view def get_virtual_price() -> uint256: view - def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def fee() -> uint256: view def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable interface Curve2: @@ -94,6 +76,13 @@ event TokenExchange: bought_id: int128 tokens_bought: uint256 +event TokenExchangeUnderlying: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + event AddLiquidity: provider: indexed(address) token_amounts: uint256[N_COINS] @@ -142,19 +131,20 @@ event ApplyNewFee: WETH20: public(immutable(address)) +MAX_POOL_COINS: constant(uint256) = 4 + N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 +MAX_COIN: constant(int128) = N_COINS - 1 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(bool) +IS_REBASING: public(immutable(bool)) BASE_POOL: immutable(address) BASE_N_COINS: immutable(uint256) -BASE_N_COINS_128: immutable(int128) -BASE_COINS: immutable(address[4]) +BASE_COINS: immutable(address[MAX_POOL_COINS]) factory: public(address) coins: public(address[N_COINS]) -stored_balances: uint256[N_COINS] # ---------------------- Pool Amplification Parameters ----------------------- @@ -239,7 +229,7 @@ def __init__( _is_rebasing: bool, _base_pool: address, _base_lp_token: address, - _base_coins: address[4], + _base_coins: address[MAX_POOL_COINS], ): """ @notice Initialize the pool contract @@ -289,8 +279,8 @@ def __init__( ERC20(coin).approve(BASE_POOL, max_value(uint256)) base_n_coins += 1 + assert base_n_coins <= MAX_POOL_COINS, "Cannot onboard base pool with more than 4 coins" BASE_N_COINS = base_n_coins - BASE_N_COINS_128 = convert(BASE_N_COINS, int128) A: uint256 = _A * A_PRECISION self.initial_A = A @@ -489,36 +479,9 @@ def _balances() -> uint256[N_COINS]: return result -@internal -def _increase_balances(balances: uint256[N_COINS]): - """ - @notice Increases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer into the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] += balances[i] - self.stored_balances = stored_balances - - -@internal -def _decrease_balances(balances: uint256[N_COINS]): - """ - @notice Decreases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer out of the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] -= balances[i] - self.stored_balances = stored_balances - - # -------------------------- AMM Main Functions ------------------------------ -@payable @external @nonreentrant('lock') def exchange( @@ -532,8 +495,7 @@ def exchange( """ @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method - Allows for native token swaps (e.g. ETH <> whatever) - If native token is not in coin list and msg.value > 0, swap will revert + Even if _use_eth is in the abi, the method does not accept native token @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -542,7 +504,7 @@ def exchange( """ return self._exchange( msg.sender, - msg.value, + 0, # <--- TODO: do we need to allow eth transfers? i, j, _dx, @@ -557,46 +519,7 @@ def exchange( @external @nonreentrant('lock') -def exchange_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool, - _sender: address, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two coins after a callback - @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth). Users of this method are dex aggregators, - arbitrageurs, or other users who do not wish to grant approvals to the contract. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: No callback specified - return self._exchange( - _sender, - 0, # mvalue is zero here - i, - j, - _dx, - _min_dy, - _use_eth, - _receiver, - msg.sender, # <---------------------------- callbacker is msg.sender. - _cb, - False - ) - - -@external -@nonreentrant('lock') -def exchange_with_rebase( +def exchange_underlying( i: int128, j: int128, _dx: uint256, @@ -605,74 +528,110 @@ def exchange_with_rebase( _receiver: address = msg.sender, ) -> uint256: """ - @notice Perform an exchange between two coins without transferring token in - @dev The contract swaps tokens based on a change in balance of coin[i]. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `exchange_on_rebase`. - The method is non-payable: does not accept native token. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" - return self._exchange( - msg.sender, - 0, - i, - j, - _dx, - _min_dy, - _use_eth, - _receiver, - empty(address), - empty(bytes32), - True, # <--------------------------------------- swap optimistically. - ) + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) + base_coins: address[MAX_POOL_COINS] = BASE_COINS -@payable -@external -@nonreentrant('lock') -def exchange_underlying() -> uint256: - # TODO: Needs exchange_underlying - # TODO: Do we need exchange_underlying with callbacks? with rebase (optimistic swap)? - pass + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + input_coin: address = empty(address) + output_coin: address = empty(address) + if i == 0: + input_coin = self.coins[0] + else: + base_i = i - MAX_COIN + meta_i = 1 + input_coin = base_coins[base_i] + if j == 0: + output_coin = self.coins[0] + else: + base_j = j - MAX_COIN + meta_j = 1 + output_coin = base_coins[base_j] -@payable -@external -@nonreentrant('lock') -def add_liquidity( - _amounts: uint256[N_COINS], - _min_mint_amount: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _receiver Address that owns the minted LP tokens - @return Amount of LP tokens received by depositing - """ - return self._add_liquidity( - msg.sender, - _amounts, - _min_mint_amount, - _use_eth, - _receiver, - msg.value, - False, # <--------------------- Does not expect optimistic transfers. + # --------------------------- Do Transfer in ----------------------------- + + dx_w_fee: uint256 = ERC20(input_coin).balanceOf(self) + assert ERC20(input_coin).transferFrom( + msg.sender, self, _dx, default_return_value=True ) + dx_w_fee = ERC20(input_coin).balanceOf(self) - dx_w_fee + + # ------------------------------------------------------------------------ + + dx: uint256 = _dx + if i == 0 or j == 0: + if i == 0: + x = xp[i] + dx * rates[i] / PRECISION + else: + # i is from BasePool + dx = self._meta_add_liquidity(dx, base_i) + x = dx * rates[MAX_COIN] / PRECISION + # Adding number of pool tokens + x += xp[MAX_COIN] + + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) + + # Either a real coin or token + dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + # Works for both pool coins and real coins + dy = (dy - dy_fee) * PRECISION / rates[meta_j] + + dy_admin_fee: uint256 = dy_fee * ADMIN_FEE / FEE_DENOMINATOR + dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] + + self.admin_balances[meta_j] += dy_admin_fee + + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount + + assert dy >= _min_dy + + else: + # If both are from the base pool + dy = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).exchange(base_i, base_j, dx, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy + + # --------------------------- Do Transfer out ---------------------------- + + assert ERC20(output_coin).transfer(_receiver, dy, default_return_value=True) + + # ------------------------------------------------------------------------ + + log TokenExchangeUnderlying(msg.sender, i, _dx, j, dy) # TODO: check this! + + return dy @external @nonreentrant('lock') -def add_liquidity_with_rebase( +def add_liquidity( _amounts: uint256[N_COINS], _min_mint_amount: uint256, _use_eth: bool = False, @@ -680,18 +639,11 @@ def add_liquidity_with_rebase( ) -> uint256: """ @notice Deposit coins into the pool - @dev The contract adds liquidity based on a change in balance of coins. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `add_liquidity_on_rebase`. - The method is non-payable: does not accept native token. @param _amounts List of amounts of coins to deposit @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" return self._add_liquidity( msg.sender, _amounts, @@ -699,7 +651,7 @@ def add_liquidity_with_rebase( _use_eth, _receiver, 0, - True, # <------------------------------ Expects optimistic transfers. + False, # <--------------------- Does not expect optimistic transfers. ) @@ -732,9 +684,6 @@ def remove_liquidity_one_coin( self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) - # Decrease coin[i] balance in self.stored_balances - self.stored_balances[i] -= dy[0] - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) self.save_p_from_price(dy[2]) @@ -769,9 +718,6 @@ def remove_liquidity_imbalance( new_balances[i] -= _amounts[i] self._transfer_out(coins[i], _amounts[i], _use_eth, _receiver) - # Decrease balances in self.stored_balances - self._decrease_balances(_amounts) - D1: uint256 = self.get_D_mem(rates, new_balances, amp) fees: uint256[N_COINS] = empty(uint256[N_COINS]) @@ -834,9 +780,6 @@ def remove_liquidity( amounts[i] = value self._transfer_out(coins[i], value, _use_eth, _receiver) - # Decrease balances in self.stored_balances - self._decrease_balances(amounts) - total_supply -= _burn_amount self.balanceOf[msg.sender] -= _burn_amount self.totalSupply = total_supply @@ -887,27 +830,12 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- - dx: uint256 = 0 - - if expect_optimistic_transfer: - - # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] - - assert dx == _dx, "Pool did not receive tokens for swap" - - else: - - # `dx` is whatever the pool received after ERC20 transfer: - dx = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth - ) - - # Update stored balances - self.stored_balances[i] += dx + # `dx` is whatever the pool received after ERC20 transfer: + dx: uint256 = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) # ------------------------------------------------------------------------ @@ -938,9 +866,6 @@ def _exchange( self._transfer_out(coins[j], dy, use_eth, receiver) - # Update Stored Balances: - self.stored_balances[j] -= dy - # ------------------------------------------------------------------------ log TokenExchange(msg.sender, i, _dx, j, dy) @@ -972,66 +897,41 @@ def _add_liquidity( # -------------------------- Do Transfers In ----------------------------- - if expect_optimistic_transfer: - - dx: uint256 = 0 - - for i in range(N_COINS): - - if amounts[i] > 0: + for i in range(N_COINS): - # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] + if amounts[i] > 0: - assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" + if coins[i] == WETH20: - new_balances[i] += dx + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + mvalue, + empty(address), + empty(bytes32), + sender, + empty(address), + use_eth + ) else: - assert total_supply != 0 # dev: initial deposit requires all coins - - else: - - for i in range(N_COINS): - - if amounts[i] > 0: - - if coins[i] == WETH20: - - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - mvalue, - empty(address), - empty(bytes32), - sender, - empty(address), - use_eth - ) - - else: - - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - sender, - empty(address), - False - ) - - else: + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + sender, + empty(address), + False + ) - assert total_supply != 0 # dev: initial deposit requires all coins + else: - # Add incoming balance - self._increase_balances(new_balances) + assert total_supply != 0 # dev: initial deposit requires all coins # ------------------------------------------------------------------------ @@ -1102,8 +1002,61 @@ def _withdraw_admin_fees(): ) self.admin_balances = empty(uint256[N_COINS]) - # Reduce stored balances: - self._decrease_balances(amounts) + + +# ------------------------ AMM Metapool Functions ---------------------------- + + +@internal +def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: + + coin_i: address = self.coins[MAX_COIN] + x: uint256 = ERC20(coin_i).balanceOf(self) + + if BASE_N_COINS == 2: + + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + Curve2(BASE_POOL).add_liquidity(base_inputs, 0) + + if BASE_N_COINS == 3: + + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + Curve3(BASE_POOL).add_liquidity(base_inputs, 0) + + else: + + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + Curve4(BASE_POOL).add_liquidity(base_inputs, 0) + + return ERC20(coin_i).balanceOf(self) - x + + +@internal +@view +def _meta_calc_token_amounts_deposit( + dx: uint256, base_i: int128, meta_vprice: uint256 +) -> uint256: + + if BASE_N_COINS == 2: + + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + return Curve2(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + + elif BASE_N_COINS == 3: + + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + return Curve3(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + + else: + + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + return Curve4(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION # --------------------------- AMM Math Functions ----------------------------- @@ -1111,7 +1064,14 @@ def _withdraw_admin_fees(): @view @internal -def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, _D: uint256) -> uint256: +def get_y( + i: int128, + j: int128, + x: uint256, + xp: uint256[N_COINS], + _amp: uint256, + _D: uint256 +) -> uint256: """ Calculate x[j] if one makes x[i] = x @@ -1654,7 +1614,7 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @external def get_dx_underlying(i: int128, j: int128, dx: uint256) -> uint256: # TODO: Needs get_dx_underlying - pass + return 0 @view @@ -1681,8 +1641,63 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @view @external def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - # TODO: Needs get_dy_underlying - pass + """ + @notice Calculate the current output dy given input dx on underlying + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + + # TODO: Ivan needs to check this implementation + + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + + x: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + + if i != 0: + base_i = i - MAX_COIN + meta_i = 1 + if j != 0: + base_j = j - MAX_COIN + meta_j = 1 + + if i == 0: + x = xp[i] + dx * (rates[0] / 10**18) + else: + if j == 0: + # i is from BasePool + x = self._meta_calc_token_amounts_deposit(dx, base_i, rates[1]) + # Accounting for deposit/withdraw fees approximately + x -= x * Curve(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) + # Adding number of pool tokens + x += xp[MAX_COIN] + else: + # If both are from the base pool + return Curve(BASE_POOL).get_dy(base_i, base_j, dx) + + # This pool is involved only when in-pool assets are used + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) + dy: uint256 = xp[meta_j] - y - 1 + dy = (dy - self.fee * dy / FEE_DENOMINATOR) + + # If output is going via the metapool + if j == 0: + dy /= (rates[0] / 10**18) + else: + # j is from BasePool + # The fee is already accounted for + dy = Curve(BASE_POOL).calc_withdraw_one_coin(dy * PRECISION / rates[1], base_j) + + return dy @view From e8fd754df8b62612b6d2cddf0ca5a8c6e57e451f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:53:12 +0200 Subject: [PATCH 030/337] add methods for integrators --- contracts/main/CurveStableSwapMetaNG.vy | 388 +++++++++++++++++------- 1 file changed, 270 insertions(+), 118 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index a64eaed0..889aba6e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -16,6 +16,9 @@ of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. 4. Adds `get_dx_underlying`. + 5. Adds `exchange_with_rebase` (expects ERC20 token transferred in and just swaps.) + 6. Adds `exchange_extended` (swap with callback) + 6. Adds `add_liquidity_with_rebase` """ from vyper.interfaces import ERC20 @@ -32,10 +35,6 @@ interface Factory: interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view -interface WETH: - def deposit(): payable - def withdraw(_amount: uint256): nonpayable - interface Curve: def coins(i: uint256) -> address: view def get_virtual_price() -> uint256: view @@ -145,6 +144,7 @@ BASE_COINS: immutable(address[MAX_POOL_COINS]) factory: public(address) coins: public(address[N_COINS]) +stored_balances: uint256[N_COINS] # ---------------------- Pool Amplification Parameters ----------------------- @@ -290,7 +290,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: check if this line is correct + self.last_prices_packed = self.pack_prices(10**18, 10**18) self.ma_last_time = block.timestamp # EIP712 @@ -315,13 +315,6 @@ def __init__( # ------------------ Token transfers in and out of the AMM ------------------- -@payable -@external -def __default__(): - if msg.value > 0: - assert WETH20 in self.coins - - @internal def _transfer_in( coin: address, @@ -359,76 +352,38 @@ def _transfer_in( """ _dx: uint256 = dx - if use_eth and coin == WETH20: # <----------- Pool receives native token. - - assert mvalue == _dx # dev: incorrect eth amount + # --------------------- Start Callback Handling ---------------------- - else: # <------- Pool receives wrapped native token and not native token. + initial_x: uint256 = ERC20(coin).balanceOf(self) - assert mvalue == 0 # dev: nonzero eth amount + if callback_sig == empty(bytes32): - initial_x: uint256 = ERC20(coin).balanceOf(self) - - # --------------------- Start Callback Handling ---------------------- - - if callback_sig == empty(bytes32): - - assert ERC20(coin).transferFrom( - sender, self, _dx, default_return_value=True - ) + assert ERC20(coin).transferFrom( + sender, self, _dx, default_return_value=True + ) - else: + else: - raw_call( - callbacker, - concat( - slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coin, _dx, dy) - ) + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, coin, _dx, dy) ) + ) - # If the coin is a fee-on-transfer token, transferring `_dx` amount can - # result in the pool receiving slightly less amount. So: recalculate dx - - _dx = ERC20(coin).balanceOf(self) - initial_x + # If the coin is a fee-on-transfer token, transferring `_dx` amount can + # result in the pool receiving slightly less amount. So: recalculate dx - assert _dx > 0 # dev: pool received 0 tokens + _dx = ERC20(coin).balanceOf(self) - initial_x - # -------------------- End Callback Handling ------------------------- + assert _dx > 0 # dev: pool received 0 tokens - if coin == WETH20: - WETH(WETH20).withdraw(_dx) # <--------- if WETH was transferred in - # previous step and `not use_eth`, withdraw WETH to ETH. + # -------------------- End Callback Handling ------------------------- - # Return _dx so it can be used by `_exchange` and `add_liquidity`. return _dx -@internal -def _transfer_out( - _coin: address, _amount: uint256, use_eth: bool, receiver: address -): - """ - @notice Transfer a single token from the pool to receiver. - @dev This function is called by `remove_liquidity` and - `remove_liquidity_one` and `_exchange` methods. - @params _coin Address of the token to transfer out - @params _amount Amount of token to transfer out - @params use_eth Whether to transfer ETH or not - @params receiver Address to send the tokens to - """ - - if use_eth and _coin == WETH20: - raw_call(receiver, b"", value=_amount) - else: - if _coin == WETH20: - WETH(WETH20).deposit(value=_amount) - - assert ERC20(_coin).transfer( - receiver, _amount, default_return_value=True - ) - - # -------------------------- AMM Special Methods ----------------------------- @@ -479,6 +434,32 @@ def _balances() -> uint256[N_COINS]: return result +@internal +def _increase_balances(balances: uint256[N_COINS]): + """ + @notice Increases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer into the pool. + """ + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] += balances[i] + self.stored_balances = stored_balances + + +@internal +def _decrease_balances(balances: uint256[N_COINS]): + """ + @notice Decreases self.stored_balances by `balances` amount + @dev This is an internal accounting method and must be called whenever there + is an ERC20 token transfer out of the pool. + """ + stored_balances: uint256[N_COINS] = self.stored_balances + for i in range(N_COINS): + stored_balances[i] -= balances[i] + self.stored_balances = stored_balances + + # -------------------------- AMM Main Functions ------------------------------ @@ -504,7 +485,7 @@ def exchange( """ return self._exchange( msg.sender, - 0, # <--- TODO: do we need to allow eth transfers? + 0, i, j, _dx, @@ -517,6 +498,85 @@ def exchange( ) +@external +@nonreentrant('lock') +def exchange_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _sender: address, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two coins after a callback + @dev Index values can be found via the `coins` public getter method + Not payable (does not accept eth). Users of this method are dex aggregators, + arbitrageurs, or other users who do not wish to grant approvals to the contract. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: No callback specified + return self._exchange( + _sender, + 0, # mvalue is zero here + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + msg.sender, # <---------------------------- callbacker is msg.sender. + _cb, + False + ) + + +@external +@nonreentrant('lock') +def exchange_with_rebase( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins without transferring token in + @dev The contract swaps tokens based on a change in balance of coin[i]. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `exchange_on_rebase`. + The method is non-payable: does not accept native token. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._exchange( + msg.sender, + 0, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32), + True, # <--------------------------------------- swap optimistically. + ) + + @external @nonreentrant('lock') def exchange_underlying( @@ -576,15 +636,13 @@ def exchange_underlying( # ------------------------------------------------------------------------ - dx: uint256 = _dx if i == 0 or j == 0: if i == 0: - x = xp[i] + dx * rates[i] / PRECISION + x = xp[i] + dx_w_fee * rates[i] / PRECISION else: - # i is from BasePool - dx = self._meta_add_liquidity(dx, base_i) - x = dx * rates[MAX_COIN] / PRECISION - # Adding number of pool tokens + + dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) + x = dx_w_fee * rates[MAX_COIN] / PRECISION x += xp[MAX_COIN] amp: uint256 = self._A() @@ -612,10 +670,16 @@ def exchange_underlying( assert dy >= _min_dy - else: - # If both are from the base pool + # xp is not used anymore, so we reuse it for price calc + xp[meta_i] = x + xp[meta_j] = y + # D is not changed because we did not apply a fee + self.save_p(xp, amp, D) + + else: # Swap only involves base pool + dy = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).exchange(base_i, base_j, dx, _min_dy) + Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) dy = ERC20(output_coin).balanceOf(self) - dy # --------------------------- Do Transfer out ---------------------------- @@ -655,6 +719,39 @@ def add_liquidity( ) +@external +@nonreentrant('lock') +def add_liquidity_with_rebase( + _amounts: uint256[N_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @dev The contract adds liquidity based on a change in balance of coins. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `add_liquidity_on_rebase`. + The method is non-payable: does not accept native token. + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._add_liquidity( + msg.sender, + _amounts, + _min_mint_amount, + _use_eth, + _receiver, + 0, + True, # <------------------------------ Expects optimistic transfers. + ) + + @external @nonreentrant('lock') def remove_liquidity_one_coin( @@ -682,7 +779,10 @@ def remove_liquidity_one_coin( log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + assert ERC20(self.coins[i]).transfer(_receiver, dy[0], default_return_value=True) + + # Decrease coin[i] balance in self.stored_balances + self.stored_balances[i] -= dy[0] log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) @@ -716,7 +816,12 @@ def remove_liquidity_imbalance( for i in range(N_COINS): if _amounts[i] != 0: new_balances[i] -= _amounts[i] - self._transfer_out(coins[i], _amounts[i], _use_eth, _receiver) + assert ERC20(coins[i]).transfer( + _receiver, _amounts[i], default_return_value=True + ) + + # Decrease balances in self.stored_balances + self._decrease_balances(_amounts) D1: uint256 = self.get_D_mem(rates, new_balances, amp) @@ -778,7 +883,10 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(coins[i], value, _use_eth, _receiver) + assert ERC20(coins[i]).transfer(_receiver, value, default_return_value=True) + + # Decrease balances in self.stored_balances + self._decrease_balances(amounts) total_supply -= _burn_amount self.balanceOf[msg.sender] -= _burn_amount @@ -830,12 +938,27 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- - # `dx` is whatever the pool received after ERC20 transfer: - dx: uint256 = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth - ) + dx: uint256 = 0 + + if expect_optimistic_transfer: + + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] + + assert dx == _dx, "Pool did not receive tokens for swap" + + else: + + # `dx` is whatever the pool received after ERC20 transfer: + dx = self._transfer_in( + coins[i], _dx, _min_dy, mvalue, + callbacker, callback_sig, + sender, receiver, use_eth + ) + + # Update stored balances + self.stored_balances[i] += dx # ------------------------------------------------------------------------ @@ -864,7 +987,10 @@ def _exchange( # --------------------------- Do Transfer out ---------------------------- - self._transfer_out(coins[j], dy, use_eth, receiver) + assert ERC20(coins[j]).transfer(receiver, dy, default_return_value=True) + + # Update Stored Balances: + self.stored_balances[j] -= dy # ------------------------------------------------------------------------ @@ -897,41 +1023,66 @@ def _add_liquidity( # -------------------------- Do Transfers In ----------------------------- - for i in range(N_COINS): + if expect_optimistic_transfer: - if amounts[i] > 0: + dx: uint256 = 0 - if coins[i] == WETH20: + for i in range(N_COINS): - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - mvalue, - empty(address), - empty(bytes32), - sender, - empty(address), - use_eth - ) + if amounts[i] > 0: + + # This branch is never reached for rebasing tokens + pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) + dx = pool_x_balance - self.stored_balances[i] + + assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" + + new_balances[i] += dx else: - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - sender, - empty(address), - False - ) + assert total_supply != 0 # dev: initial deposit requires all coins - else: + else: - assert total_supply != 0 # dev: initial deposit requires all coins + for i in range(N_COINS): + + if amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + mvalue, + empty(address), + empty(bytes32), + sender, + empty(address), + use_eth + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + amounts[i], + 0, + 0, + empty(address), + empty(bytes32), + sender, + empty(address), + False + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Add incoming balance + self._increase_balances(new_balances) # ------------------------------------------------------------------------ @@ -1003,6 +1154,9 @@ def _withdraw_admin_fees(): self.admin_balances = empty(uint256[N_COINS]) + # Decrease balances in self.stored_balances + self._decrease_balances(amounts) + # ------------------------ AMM Metapool Functions ---------------------------- @@ -1577,9 +1731,7 @@ def ema_price() -> uint256: @view def get_p() -> uint256: amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem( - self._stored_rates(), self._balances() - ) + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) D: uint256 = self.get_D(xp, amp) return self._get_p(xp, amp, D) From 9f97a839f9e952bb51b176e951aa322d433599c2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:26:49 +0200 Subject: [PATCH 031/337] apply fee instantaneously --- contracts/main/CurveStableSwap2NG.vy | 38 +++++-------------------- contracts/main/CurveStableSwapMetaNG.vy | 38 +++++-------------------- 2 files changed, 14 insertions(+), 62 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 7b8e58c3..1049be84 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -108,9 +108,6 @@ event StopRampA: A: uint256 t: uint256 -event CommitNewFee: - new_fee: uint256 - event ApplyNewFee: fee: uint256 @@ -127,6 +124,8 @@ IS_REBASING: public(immutable(bool)) factory: public(address) coins: public(address[N_COINS]) stored_balances: uint256[N_COINS] +fee: public(uint256) # fee * 1e10 +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 # ---------------------- Pool Amplification Parameters ----------------------- @@ -139,21 +138,11 @@ future_A: public(uint256) initial_A_time: public(uint256) future_A_time: public(uint256) -# ---------------------------- Fee Variables --------------------------------- +# ---------------------------- Admin Variables ------------------------------- ADMIN_FEE: constant(uint256) = 5000000000 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 MAX_FEE: constant(uint256) = 5 * 10 ** 9 -fee: public(uint256) # fee * 1e10 -future_fee: public(uint256) - -# ---------------------------- Admin Variables ------------------------------- - MIN_RAMP_TIME: constant(uint256) = 86400 -ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 -admin_action_deadline: public(uint256) - admin_balances: public(uint256[N_COINS]) # ----------------------- Oracle Specific vars ------------------------------- @@ -1793,26 +1782,13 @@ def stop_ramp_A(): @external -def commit_new_fee(_new_fee: uint256): - assert msg.sender == Factory(self.factory).admin() - assert _new_fee <= MAX_FEE - assert self.admin_action_deadline == 0 +def apply_new_fee(_new_fee: uint256): - self.future_fee = _new_fee - self.admin_action_deadline = block.timestamp + ADMIN_ACTIONS_DEADLINE_DT - log CommitNewFee(_new_fee) - - -@external -def apply_new_fee(): assert msg.sender == Factory(self.factory).admin() - deadline: uint256 = self.admin_action_deadline - assert deadline != 0 and block.timestamp >= deadline + assert _new_fee <= MAX_FEE + self.fee = _new_fee - fee: uint256 = self.future_fee - self.fee = fee - self.admin_action_deadline = 0 - log ApplyNewFee(fee) + log ApplyNewFee(_new_fee) @external diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 889aba6e..07e61326 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -119,9 +119,6 @@ event StopRampA: A: uint256 t: uint256 -event CommitNewFee: - new_fee: uint256 - event ApplyNewFee: fee: uint256 @@ -145,6 +142,8 @@ BASE_COINS: immutable(address[MAX_POOL_COINS]) factory: public(address) coins: public(address[N_COINS]) stored_balances: uint256[N_COINS] +fee: public(uint256) # fee * 1e10 +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 # ---------------------- Pool Amplification Parameters ----------------------- @@ -157,21 +156,11 @@ future_A: public(uint256) initial_A_time: public(uint256) future_A_time: public(uint256) -# ---------------------------- Fee Variables --------------------------------- +# ---------------------------- Admin Variables ------------------------------- ADMIN_FEE: constant(uint256) = 5000000000 - -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 MAX_FEE: constant(uint256) = 5 * 10 ** 9 -fee: public(uint256) # fee * 1e10 -future_fee: public(uint256) - -# ---------------------------- Admin Variables ------------------------------- - MIN_RAMP_TIME: constant(uint256) = 86400 -ADMIN_ACTIONS_DEADLINE_DT: constant(uint256) = 86400 * 3 -admin_action_deadline: public(uint256) - admin_balances: public(uint256[N_COINS]) # ----------------------- Oracle Specific vars ------------------------------- @@ -2020,26 +2009,13 @@ def stop_ramp_A(): @external -def commit_new_fee(_new_fee: uint256): - assert msg.sender == Factory(self.factory).admin() - assert _new_fee <= MAX_FEE - assert self.admin_action_deadline == 0 +def apply_new_fee(_new_fee: uint256): - self.future_fee = _new_fee - self.admin_action_deadline = block.timestamp + ADMIN_ACTIONS_DEADLINE_DT - log CommitNewFee(_new_fee) - - -@external -def apply_new_fee(): assert msg.sender == Factory(self.factory).admin() - deadline: uint256 = self.admin_action_deadline - assert deadline != 0 and block.timestamp >= deadline + assert _new_fee <= MAX_FEE + self.fee = _new_fee - fee: uint256 = self.future_fee - self.fee = fee - self.admin_action_deadline = 0 - log ApplyNewFee(fee) + log ApplyNewFee(_new_fee) @external From 785369a7b9c8acd2903beb087065e972faf0288a Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 28 Jun 2023 21:35:32 +0200 Subject: [PATCH 032/337] fixes and pools tests init --- .github/workflows/lint.yaml | 4 +- .../workflows/{gauge.yaml => test_gauge.yaml} | 2 +- .github/workflows/test_pools_2.yaml | 41 ++++++ tests/conftest.py | 24 ++-- tests/fixtures/accounts.py | 24 +++- tests/fixtures/pools.py | 7 + tests/fixtures/tokens.py | 3 +- tests/pools/oracle/__init__.py | 0 tests/pools/rebasing/__init__.py | 0 tests/pools/test_token.py | 120 ++++++++++++++++++ 10 files changed, 203 insertions(+), 22 deletions(-) rename .github/workflows/{gauge.yaml => test_gauge.yaml} (98%) create mode 100644 .github/workflows/test_pools_2.yaml create mode 100644 tests/pools/oracle/__init__.py create mode 100644 tests/pools/rebasing/__init__.py create mode 100644 tests/pools/test_token.py diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 153f6f3e..a0d59e2c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,7 +1,7 @@ -on: [pull_request, push] - name: lint +on: [pull_request, push] + jobs: lint: diff --git a/.github/workflows/gauge.yaml b/.github/workflows/test_gauge.yaml similarity index 98% rename from .github/workflows/gauge.yaml rename to .github/workflows/test_gauge.yaml index b2f0e9fc..0bd5e869 100644 --- a/.github/workflows/gauge.yaml +++ b/.github/workflows/test_gauge.yaml @@ -1,4 +1,4 @@ -name: gauge +name: test_gauge on: pull_request: diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml new file mode 100644 index 00000000..1d25aa83 --- /dev/null +++ b/.github/workflows/test_pools_2.yaml @@ -0,0 +1,41 @@ +name: test_gauge + +on: [pull_request, push] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Cache Compiler Installations + uses: actions/cache@v3 + with: + path: | + ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: | + pip install poetry==1.5.1 + poetry config virtualenvs.in-project true + poetry install --no-interaction + + - name: Run All Token Tests 18,18 + run: | + source .venv/bin/activate + pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto + + - name: Run Plain Tests 18,6 + run: | + source .venv/bin/activate + pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto diff --git a/tests/conftest.py b/tests/conftest.py index 26a57c5a..8211439c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,7 @@ def pytest_addoption(parser): "--pool-size", action="store", default="2", - help="comma-separated list of plain pool sizes to test against", + help="pool size to test against", ) # TODO: add meta implementation parser.addoption( @@ -52,18 +52,16 @@ def pytest_addoption(parser): def pytest_generate_tests(metafunc): + pool_size = int(metafunc.config.getoption("pool_size")) if "pool_size" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("pool_size").split(",") - pool_sizes = [int(v) for v in cli_options] - # TODO: remove after adding implementations - assert pool_sizes == [2], "Only 2-coin pools supported" + assert pool_size == 2, "Only 2-coin pools supported" metafunc.parametrize( "pool_size", - pool_sizes, + [pool_size], indirect=True, - ids=[f"(PoolSize={i})" for i in cli_options], + ids=[f"(PoolSize={pool_size})"], ) if "pool_type" in metafunc.fixturenames: @@ -79,12 +77,12 @@ def pytest_generate_tests(metafunc): if "pool_token_types" in metafunc.fixturenames: cli_options = metafunc.config.getoption("token_types").split(",") - # TODO: only 2-coin pools are supported - combs = list(combinations(cli_options, 2)) - # do not include (eth,eth) pair - for t in cli_options: - if t != "eth": - combs.append((t, t)) + combs = list(combinations(cli_options, pool_size)) + if pool_size == 2: + # do not include (eth,eth) pair + for t in cli_options: + if t != "eth": + combs.append((t, t)) metafunc.parametrize( "pool_token_types", diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 27bc4f36..d6ccd260 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -16,11 +16,6 @@ def owner() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="module") -def factory_admin(factory) -> AddressType: - return factory.admin() - - @pytest.fixture(scope="module") def fee_receiver() -> AddressType: return boa.env.generate_address() @@ -61,6 +56,11 @@ def frank(): return boa.env.generate_address() +@pytest.fixture(scope="module") +def accounts(bob, charlie, dave, erin, frank): + return [bob, charlie, dave, erin, frank] + + # <--------------------- Functions ---------------------> @pytest.fixture(scope="module") def mint_owner(owner, pool_tokens, initial_amounts): @@ -74,3 +74,17 @@ def approve_owner(owner, pool_tokens, swap): for pool_token in pool_tokens: with boa.env.prank(owner): pool_token.approve(swap.address, 2**256 - 1) + + +@pytest.fixture(scope="module") +def mint_alice(alice, pool_tokens, initial_amounts): + mint_for_testing(alice, 10**18, None, True) + for pool_token, amount in zip(pool_tokens, initial_amounts): + mint_for_testing(alice, amount, pool_token, False) + + +@pytest.fixture(scope="module") +def approve_alice(alice, pool_tokens, swap): + for pool_token in pool_tokens: + with boa.env.prank(alice): + pool_token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index fa0e27a0..58796c96 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -10,6 +10,7 @@ def swap( owner, mint_owner, factory, + weth, pool_token_types, pool_tokens, amm_interface_plain, @@ -71,3 +72,9 @@ def swap( def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): with boa.env.prank(owner): swap.add_liquidity(deposit_amounts, 0) + + +@pytest.fixture +def add_initial_liquidity_alice(alice, approve_alice, mint_alice, deposit_amounts, swap): + with boa.env.prank(alice): + swap.add_liquidity(deposit_amounts, 0) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 627040cd..219f6584 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -58,7 +58,8 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke if t == 0: pool_tokens.append(plain_tokens[i]) elif t == 1: - pool_tokens.append(weth) + # Enforce eth as 0th token + pool_tokens = [weth] + pool_tokens elif t == 2: pool_tokens.append(oracle_tokens[i]) elif t == 3: diff --git a/tests/pools/oracle/__init__.py b/tests/pools/oracle/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/rebasing/__init__.py b/tests/pools/rebasing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py new file mode 100644 index 00000000..44b77ca4 --- /dev/null +++ b/tests/pools/test_token.py @@ -0,0 +1,120 @@ +import boa +import pytest + +# from eip712.messages import EIP712Message + + +@pytest.mark.usefixtures("add_initial_liquidity_alice") +class TestPoolToken: + class TestTokenApprove: + @pytest.mark.parametrize("idx", range(4)) + def test_initial_approval_is_zero(self, swap, alice, accounts, idx): + assert swap.allowance(alice, accounts[idx]) == 0 + + def test_approve(self, swap, alice, bob): + swap.approve(bob, 10**19, sender=alice) + assert swap.allowance(alice, bob) == 10**19 + + def test_modify_approve_zero_nonzero(self, swap, alice, bob): + with boa.env.prank(alice): + swap.approve(bob, 10**19) + swap.approve(bob, 0) + swap.approve(bob, 12345678) + + assert swap.allowance(alice, bob) == 12345678 + + def test_revoke_approve(self, swap, alice, bob): + with boa.env.prank(alice): + swap.approve(bob, 10**19) + swap.approve(bob, 0) + + assert swap.allowance(alice, bob) == 0 + + def test_approve_self(self, swap, alice): + swap.approve(alice, 10**19, sender=alice) + assert swap.allowance(alice, alice) == 10**19 + + def test_only_affects_target(self, swap, alice, bob): + swap.approve(bob, 10**19, sender=alice) + assert swap.allowance(bob, alice) == 0 + + def test_returns_true(self, swap, alice, bob): + tx = swap.approve(bob, 10**19, sender=alice) + assert tx is True + + # TODO: add event processing + # def test_approval_event_fires(self, alice, bob, swap): + # tx = swap.approve(bob, 10 ** 19, sender=alice) + # + # events = swap.get_logs() + # print(events, type(events[0])) + # + # assert len(tx.events) == 1 + # assert tx.events["Approval"].values() == [alice, bob, 10 ** 19] + + def test_infinite_approval(self, swap, alice, bob): + swap.approve(bob, 2**256 - 1, sender=alice) + swap.transferFrom(alice, bob, 10**18, sender=bob) + + assert swap.allowance(alice, bob) == 2**256 - 1 + + # def test_permit(self, eth_acc, bob, swap): + # class Permit(EIP712Message): + # # EIP-712 Domain Fields + # _name_: "string" = swap.name() # noqa: F821 + # _version_: "string" = swap.version() # noqa: F821 + # _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 + # _verifyingContract_: "address" = swap.address # noqa: F821 + # + # # EIP-2612 Data Fields + # owner: "address" # noqa: F821 + # spender: "address" # noqa: F821 + # value: "uint256" # noqa: F821 + # nonce: "uint256" # noqa: F821 + # deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 + # + # permit = Permit(owner=eth_acc.address, spender=bob, value=2 ** 256 - 1, nonce=0) + # sig = eth_acc.sign_message(permit.signable_message) + # + # with boa.env.prank(bob): + # tx = swap.permit(eth_acc.address, bob, 2 ** 256 - 1, 2 ** 256 - 1, sig.v, sig.r, sig.s) + # + # assert swap.allowance(eth_acc.address, bob) == 2 ** 256 - 1 + # assert tx is True + # assert len(swap.get_logs()) == 1 + # assert swap.get_logs().events["Approval"].values() == [eth_acc.address.address, bob, 2 ** 256 - 1] + # assert swap.nonces(eth_acc.address) == 1 + # + # def test_permit_contract(accounts, bob, chain, swap, web3): + # src = """ + # @view + # @external + # def isValidSignature(_hash: bytes32, _sig: Bytes[65]) -> bytes32: + # return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 + # """ + # mock_contract = brownie.compile_source(src, vyper_version="0.3.1").Vyper.deploy({"from": bob}) + # alice = accounts.add("0x416b8a7d9290502f5661da81f0cf43893e3d19cb9aea3c426cfb36e8186e9c09") + # + # class Permit(EIP712Message): + # # EIP-712 Domain Fields + # _name_: "string" = swap.name() # noqa: F821 + # _version_: "string" = swap.version() # noqa: F821 + # _chainId_: "uint256" = chain.id # noqa: F821 + # _verifyingContract_: "address" = swap.address # noqa: F821 + # + # # EIP-2612 Data Fields + # owner: "address" # noqa: F821 + # spender: "address" # noqa: F821 + # value: "uint256" # noqa: F821 + # nonce: "uint256" # noqa: F821 + # deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 + # + # permit = Permit(owner=alice.address, spender=bob.address, value=2 ** 256 - 1, nonce=0) + # sig = alice.sign_message(permit) + # + # tx = swap.permit( + # mock_contract, bob, 2 ** 256 - 1, 2 ** 256 - 1, sig.v, sig.r, sig.s, {"from": bob} + # ) + # + # # make sure this is hit when owner is a contract + # assert tx.subcalls[-1]["function"] == "isValidSignature(bytes32,bytes)" From 706c9000bac4e52ce3bc25299e922fde8c5bcafd Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 28 Jun 2023 21:36:53 +0200 Subject: [PATCH 033/337] fix action name --- .github/workflows/test_pools_2.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml index 1d25aa83..a66e1665 100644 --- a/.github/workflows/test_pools_2.yaml +++ b/.github/workflows/test_pools_2.yaml @@ -1,4 +1,4 @@ -name: test_gauge +name: test_pools_2 on: [pull_request, push] From 551c347946af462a0b345a61f641038ffedc33b1 Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 29 Jun 2023 00:12:18 +0200 Subject: [PATCH 034/337] fix mocks and fixtures --- contracts/mocks/ERC20Rebasing.vy | 13 ++++++++----- tests/conftest.py | 12 +++++++++--- tests/fixtures/constants.py | 12 ++---------- tests/fixtures/tokens.py | 6 +++--- tests/pools/test_token.py | 2 +- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index 99a35255..bfb8138d 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -80,13 +80,16 @@ def transfer(_to: address, _value: uint256) -> bool: def transferFrom(_from: address, _to: address, _value: uint256) -> bool: self._rebase() _shares: uint256 = self._get_shares_by_coins(_value) + _shares = min(self.shares[_from], _shares) + # Value can be less than expected even if self.shares[_from] > _shares + _new_value: uint256 = self._get_coins_by_shares(_shares) self.shares[_from] -= _shares self.shares[_to] += _shares - self.allowances[_from][msg.sender] -= _value + self.allowances[_from][msg.sender] -= _new_value - log Transfer(_from, _to, _value) - log TransferShares(_from, _to, _shares) + log Transfer(_from, _to, _new_value) + log TransferShares(_from, _to, _new_value) return True @@ -141,9 +144,9 @@ def set_total_coin(total_coin: uint256) -> bool: @external def _mint_for_testing(_target: address, _value: uint256) -> bool: - self.totalCoin += _value - _shares: uint256 = self._get_shares_by_coins(_value) + + self.totalCoin += _value self.totalShares += _shares self.shares[_target] += _shares diff --git a/tests/conftest.py b/tests/conftest.py index 8211439c..c688f60e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -91,10 +91,10 @@ def pytest_generate_tests(metafunc): ids=[f"(PoolTokenTypes={c})" for c in combs], ) - if "decimals" in metafunc.fixturenames: + if "initial_decimals" in metafunc.fixturenames: cli_options = metafunc.config.getoption("decimals") metafunc.parametrize( - "decimals", + "initial_decimals", [[int(i) for i in cli_options.split(",")]], indirect=True, ids=[f"(Decimals={cli_options})"], @@ -133,10 +133,16 @@ def return_type(request): @pytest.fixture(scope="session") -def decimals(request): +def initial_decimals(request): return request.param +@pytest.fixture(scope="session") +def decimals(initial_decimals, pool_token_types): + # eth and oracle pools are always 18 + return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] + + @pytest.fixture(scope="module") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 62c0b27b..138f2a0f 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -9,16 +9,8 @@ def initial_amounts(decimals: list[int]) -> list[int]: @pytest.fixture(scope="module") -def initial_amounts_underlying(underlying_decimals: list[int]) -> list[int]: - amts = [INITIAL_AMOUNT * 10**precision for precision in underlying_decimals] - for i in range(1, len(underlying_decimals)): - amts[i] //= len(underlying_decimals) - 1 - return amts - - -@pytest.fixture(scope="module") -def deposit_amounts(decimals: list[int]) -> list[int]: - return [INITIAL_AMOUNT // 2 * 10**18 for _ in decimals] # xp is always 18 +def deposit_amounts(initial_amounts: list[int]) -> list[int]: + return [ia // 2 for ia in initial_amounts] @pytest.fixture(scope="module") diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 219f6584..badbc6e3 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -26,7 +26,7 @@ def oracle_tokens(deployer, decimals): "contracts/mocks/ERC20Oracle.vy", "OTA", "OTA", - decimals[0], + 18, 1006470359024000000, ) ) @@ -35,7 +35,7 @@ def oracle_tokens(deployer, decimals): "contracts/mocks/ERC20Oracle.vy", "OTB", "OTB", - decimals[0], + 18, 1007580460035000000, ) ) @@ -47,7 +47,7 @@ def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "downETH", "downETH", decimals[0], False)) - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", decimals[0], True)) + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", decimals[1], True)) return tokens diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py index 44b77ca4..000a73c7 100644 --- a/tests/pools/test_token.py +++ b/tests/pools/test_token.py @@ -4,7 +4,6 @@ # from eip712.messages import EIP712Message -@pytest.mark.usefixtures("add_initial_liquidity_alice") class TestPoolToken: class TestTokenApprove: @pytest.mark.parametrize("idx", range(4)) @@ -52,6 +51,7 @@ def test_returns_true(self, swap, alice, bob): # assert len(tx.events) == 1 # assert tx.events["Approval"].values() == [alice, bob, 10 ** 19] + @pytest.mark.usefixtures("add_initial_liquidity_alice") def test_infinite_approval(self, swap, alice, bob): swap.approve(bob, 2**256 - 1, sender=alice) swap.transferFrom(alice, bob, 10**18, sender=bob) From bc31aeb2fdf4d9404bb58b41f7873b97277f772f Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 29 Jun 2023 15:07:45 +0200 Subject: [PATCH 035/337] add events handling --- tests/pools/test_token.py | 124 ++++++++++++++++++++---------------- tests/utils/transactions.py | 31 +++++++++ 2 files changed, 101 insertions(+), 54 deletions(-) create mode 100644 tests/utils/transactions.py diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py index 000a73c7..6bc386a4 100644 --- a/tests/pools/test_token.py +++ b/tests/pools/test_token.py @@ -1,7 +1,10 @@ import boa import pytest +from eip712.messages import EIP712Message -# from eip712.messages import EIP712Message +from tests.utils.transactions import call_returning_result_and_logs + +# from eth_account._utils.signing import to_bytes32 class TestPoolToken: @@ -41,15 +44,13 @@ def test_returns_true(self, swap, alice, bob): tx = swap.approve(bob, 10**19, sender=alice) assert tx is True - # TODO: add event processing - # def test_approval_event_fires(self, alice, bob, swap): - # tx = swap.approve(bob, 10 ** 19, sender=alice) - # - # events = swap.get_logs() - # print(events, type(events[0])) - # - # assert len(tx.events) == 1 - # assert tx.events["Approval"].values() == [alice, bob, 10 ** 19] + def test_approval_event_fires(self, alice, bob, swap): + value = 10**19 + res, events = call_returning_result_and_logs(swap, "approve", bob, value, sender=alice) + + assert res is True + assert len(events) == 1 + assert repr(events[0]) == f"Approval(owner={alice}, spender={bob}, value={value})" @pytest.mark.usefixtures("add_initial_liquidity_alice") def test_infinite_approval(self, swap, alice, bob): @@ -58,63 +59,78 @@ def test_infinite_approval(self, swap, alice, bob): assert swap.allowance(alice, bob) == 2**256 - 1 + @staticmethod + def permit_class(swap) -> type[EIP712Message]: + class Permit(EIP712Message): + # EIP-712 Domain Fields + _name_: "string" = swap.name() # noqa: F821 + _version_: "string" = swap.version() # noqa: F821 + _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 + _verifyingContract_: "address" = swap.address # noqa: F821 + + # EIP-2612 Data Fields + owner: "address" # noqa: F821 + spender: "address" # noqa: F821 + value: "uint256" # noqa: F821 + nonce: "uint256" # noqa: F821 + deadline: "uint256" = 2**256 - 1 # noqa: F821 + + return Permit + # def test_permit(self, eth_acc, bob, swap): - # class Permit(EIP712Message): - # # EIP-712 Domain Fields - # _name_: "string" = swap.name() # noqa: F821 - # _version_: "string" = swap.version() # noqa: F821 - # _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 - # _verifyingContract_: "address" = swap.address # noqa: F821 + # value = 2**256 - 1 + # permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) + # sig = eth_acc.sign_message(permit.signable_message) # - # # EIP-2612 Data Fields - # owner: "address" # noqa: F821 - # spender: "address" # noqa: F821 - # value: "uint256" # noqa: F821 - # nonce: "uint256" # noqa: F821 - # deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 + # from eth_account import Account # - # permit = Permit(owner=eth_acc.address, spender=bob, value=2 ** 256 - 1, nonce=0) - # sig = eth_acc.sign_message(permit.signable_message) + # print(Account.recover_message(permit.signable_message, vrs=(sig.v, sig.r, sig.s))) + # + # swap.permit( + # eth_acc.address, + # bob, + # 2**256 - 1, + # 2**256 - 1, + # sig.v, + # to_bytes32(sig.r), + # to_bytes32(sig.s), + # sender=bob, + # ) # - # with boa.env.prank(bob): - # tx = swap.permit(eth_acc.address, bob, 2 ** 256 - 1, 2 ** 256 - 1, sig.v, sig.r, sig.s) + # res, events = call_returning_result_and_logs( + # swap, + # "permit", + # eth_acc.address, + # bob, + # 2**256 - 1, + # 2**256 - 1, + # sig.v, + # to_bytes32(sig.r), + # to_bytes32(sig.s), + # sender=bob, + # ) # - # assert swap.allowance(eth_acc.address, bob) == 2 ** 256 - 1 - # assert tx is True - # assert len(swap.get_logs()) == 1 - # assert swap.get_logs().events["Approval"].values() == [eth_acc.address.address, bob, 2 ** 256 - 1] + # assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 + # assert res is True + # assert len(events) == 1 + # assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" # assert swap.nonces(eth_acc.address) == 1 # - # def test_permit_contract(accounts, bob, chain, swap, web3): + # def test_permit_contract(self, accounts, eth_acc, bob, swap): # src = """ # @view # @external # def isValidSignature(_hash: bytes32, _sig: Bytes[65]) -> bytes32: # return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 # """ - # mock_contract = brownie.compile_source(src, vyper_version="0.3.1").Vyper.deploy({"from": bob}) - # alice = accounts.add("0x416b8a7d9290502f5661da81f0cf43893e3d19cb9aea3c426cfb36e8186e9c09") + # mock_contract = boa.vyper.deploy(src, sender=bob) # - # class Permit(EIP712Message): - # # EIP-712 Domain Fields - # _name_: "string" = swap.name() # noqa: F821 - # _version_: "string" = swap.version() # noqa: F821 - # _chainId_: "uint256" = chain.id # noqa: F821 - # _verifyingContract_: "address" = swap.address # noqa: F821 - # - # # EIP-2612 Data Fields - # owner: "address" # noqa: F821 - # spender: "address" # noqa: F821 - # value: "uint256" # noqa: F821 - # nonce: "uint256" # noqa: F821 - # deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 - # - # permit = Permit(owner=alice.address, spender=bob.address, value=2 ** 256 - 1, nonce=0) - # sig = alice.sign_message(permit) + # permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=2**256 - 1, nonce=0) + # sig = eth_acc.sign_message(permit.signable_message) # - # tx = swap.permit( - # mock_contract, bob, 2 ** 256 - 1, 2 ** 256 - 1, sig.v, sig.r, sig.s, {"from": bob} + # res, events = call_returning_result_and_logs( + # swap, "permit", mock_contract.address, bob, 2**256 - 1, 2**256 - 1, sig.v, sig.r, sig.s, sender=bob # ) - # - # # make sure this is hit when owner is a contract - # assert tx.subcalls[-1]["function"] == "isValidSignature(bytes32,bytes)" + # assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 + # assert res is True + # assert len(events) == 1 diff --git a/tests/utils/transactions.py b/tests/utils/transactions.py new file mode 100644 index 00000000..97d44e72 --- /dev/null +++ b/tests/utils/transactions.py @@ -0,0 +1,31 @@ +from typing import Any + +from boa.vyper.contract import VyperContract, VyperFunction +from boa.vyper.event import Event + + +def call_returning_result_and_logs( + contract: VyperContract, function_name: str, *args, value=0, gas=None, sender=None, **kwargs +) -> tuple[Any, list[Event]]: + func: VyperFunction = getattr(contract, function_name) + calldata_bytes = func._prepare_calldata(*args, **kwargs) + override_bytecode = getattr(func, "override_bytecode", None) + + with func.contract._anchor_source_map(func._source_map): + computation = func.env.execute_code( + to_address=func.contract.address, + sender=sender, + data=calldata_bytes, + value=value, + gas=gas, + is_modifying=func.func_t.is_mutable, + override_bytecode=override_bytecode, + contract=func.contract, + ) + + typ = func.func_t.return_type + res = func.contract.marshal_to_python(computation, typ) + + events = contract.get_logs(computation) + + return res, events From 156982c24941a17cb6a1c7e69ddfef147a363eb2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 29 Jun 2023 16:06:36 +0200 Subject: [PATCH 036/337] disable base_coin <> base_lp_token pairing; remove add_liquidity_with_rebase in stableswapng; meta-ng: wip adding rebasing and callback methods --- contracts/main/CurveStableSwap2NG.vy | 60 +-- contracts/main/CurveStableSwapFactoryNG.vy | 1 + contracts/main/CurveStableSwapMetaNG.vy | 516 +++++++++++---------- 3 files changed, 285 insertions(+), 292 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 1049be84..28cff0ad 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -26,10 +26,7 @@ b. If pool contains rebasing token and `IS_REBASING` is False then this is an incorrect implementation and rebases can be stolen. - 7. Adds feature: `add_liquidity_with_rebase`. This is a version of - `add_liquidity` with optimistic ERC20 token transfers. As with - `exchange_with_rebase`, `IS_REBASING = True` disables this method. - 8. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. """ @@ -115,11 +112,12 @@ event ApplyNewFee: # ---------------------------- Pool Variables -------------------------------- WETH20: public(immutable(address)) +MAX_POOL_COINS: constant(uint256) = 4 N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: public(immutable(bool)) +IS_REBASING: immutable(bool[MAX_POOL_COINS]) factory: public(address) coins: public(address[N_COINS]) @@ -189,15 +187,15 @@ CACHED_DOMAIN_SEPARATOR: immutable(bytes32) def __init__( _name: String[32], _symbol: String[10], - _coins: address[4], - _rate_multipliers: uint256[4], + _coins: address[MAX_POOL_COINS], + _rate_multipliers: uint256[MAX_POOL_COINS], _A: uint256, _fee: uint256, _weth: address, _ma_exp_time: uint256, - _method_ids: bytes4[4], - _oracles: address[4], - _is_rebasing: bool + _method_ids: bytes4[MAX_POOL_COINS], + _oracles: address[MAX_POOL_COINS], + _is_rebasing: bool[MAX_POOL_COINS] ): """ @notice Initialize the pool contract @@ -219,7 +217,8 @@ def __init__( of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _is_rebasing If any of the coins rebases, then this should be set to True. + @param _is_rebasing Array of booleans where _is_rebasing[i] is True if _coins[i] is + a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. """ WETH20 = _weth @@ -568,7 +567,7 @@ def exchange_with_rebase( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" + assert not IS_REBASING[i], "Call disabled if IS_REBASING[i] is True" return self._exchange( msg.sender, 0, @@ -611,39 +610,6 @@ def add_liquidity( ) -@external -@nonreentrant('lock') -def add_liquidity_with_rebase( - _amounts: uint256[N_COINS], - _min_mint_amount: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Deposit coins into the pool - @dev The contract adds liquidity based on a change in balance of coins. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `add_liquidity_on_rebase`. - The method is non-payable: does not accept native token. - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _receiver Address that owns the minted LP tokens - @return Amount of LP tokens received by depositing - """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" - return self._add_liquidity( - msg.sender, - _amounts, - _min_mint_amount, - _use_eth, - _receiver, - 0, - True, # <------------------------------ Expects optimistic transfers. - ) - - @external @nonreentrant('lock') def remove_liquidity_one_coin( @@ -833,9 +799,7 @@ def _exchange( if expect_optimistic_transfer: # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] - + dx = ERC20(coins[i]).balanceOf(self) - self.stored_balances[i] assert dx == _dx, "Pool did not receive tokens for swap" else: diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index d2ffa07a..6ee37c03 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -655,6 +655,7 @@ def deploy_metapool( @param _is_rebasing If coin rebases, then this should be set to True. @return Address of the deployed pool """ + assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" implementation: address = self.base_pool_data[_base_pool].implementations[_implementation_idx] diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 07e61326..b53a55c5 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -309,12 +309,10 @@ def _transfer_in( coin: address, dx: uint256, dy: uint256, - mvalue: uint256, callbacker: address, callback_sig: bytes32, sender: address, receiver: address, - use_eth: bool, ) -> uint256: """ @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig` @@ -366,7 +364,7 @@ def _transfer_in( _dx = ERC20(coin).balanceOf(self) - initial_x - assert _dx > 0 # dev: pool received 0 tokens + assert _dx > 0 # dev: pool received 0 tokens # TODO: check! # -------------------- End Callback Handling ------------------------- @@ -474,12 +472,10 @@ def exchange( """ return self._exchange( msg.sender, - 0, i, j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -513,12 +509,10 @@ def exchange_extended( assert _cb != empty(bytes32) # dev: No callback specified return self._exchange( _sender, - 0, # mvalue is zero here i, j, _dx, _min_dy, - _use_eth, _receiver, msg.sender, # <---------------------------- callbacker is msg.sender. _cb, @@ -553,12 +547,10 @@ def exchange_with_rebase( assert not IS_REBASING, "Call disabled if IS_REBASING is True" return self._exchange( msg.sender, - 0, i, j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -584,133 +576,97 @@ def exchange_underlying( @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @param _use_eth Use native token transfers (if pool has WETH20) + For MetaNG: native token wrapping/unwrapping is disabled; this + parameter can be whatever. @param _receiver Address that receives `j` @return Actual amount of `j` received """ - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - - base_coins: address[MAX_POOL_COINS] = BASE_COINS - - dy: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - x: uint256 = 0 - input_coin: address = empty(address) - output_coin: address = empty(address) - - if i == 0: - input_coin = self.coins[0] - else: - base_i = i - MAX_COIN - meta_i = 1 - input_coin = base_coins[base_i] - if j == 0: - output_coin = self.coins[0] - else: - base_j = j - MAX_COIN - meta_j = 1 - output_coin = base_coins[base_j] - - # --------------------------- Do Transfer in ----------------------------- - - dx_w_fee: uint256 = ERC20(input_coin).balanceOf(self) - assert ERC20(input_coin).transferFrom( - msg.sender, self, _dx, default_return_value=True + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + empty(address), + empty(bytes32), + False ) - dx_w_fee = ERC20(input_coin).balanceOf(self) - dx_w_fee - - # ------------------------------------------------------------------------ - - if i == 0 or j == 0: - if i == 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION - else: - - dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_COIN] / PRECISION - x += xp[MAX_COIN] - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * ADMIN_FEE / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - self.admin_balances[meta_j] += dy_admin_fee - - # Withdraw from the base pool if needed - if j > 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= _min_dy - - # xp is not used anymore, so we reuse it for price calc - xp[meta_i] = x - xp[meta_j] = y - # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) - - else: # Swap only involves base pool - - dy = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # --------------------------- Do Transfer out ---------------------------- - - assert ERC20(output_coin).transfer(_receiver, dy, default_return_value=True) - - # ------------------------------------------------------------------------ - - log TokenExchangeUnderlying(msg.sender, i, _dx, j, dy) # TODO: check this! - - return dy +@external +@nonreentrant('lock') +def exchange_underlying_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + msg.sender, + _cb, + False + ) @external @nonreentrant('lock') -def add_liquidity( - _amounts: uint256[N_COINS], - _min_mint_amount: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender +def exchange_underlying_with_rebase( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _receiver: address, ) -> uint256: """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _receiver Address that owns the minted LP tokens - @return Amount of LP tokens received by depositing + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received """ - return self._add_liquidity( + assert not IS_REBASING, "Call disabled if IS_REBASING is True" + return self._exchange_underlying( msg.sender, - _amounts, - _min_mint_amount, - _use_eth, + i, + j, + _dx, + _min_dy, _receiver, - 0, - False, # <--------------------- Does not expect optimistic transfers. + empty(address), + empty(bytes32), + True ) @external @nonreentrant('lock') -def add_liquidity_with_rebase( +def add_liquidity( _amounts: uint256[N_COINS], _min_mint_amount: uint256, _use_eth: bool = False, @@ -718,27 +674,106 @@ def add_liquidity_with_rebase( ) -> uint256: """ @notice Deposit coins into the pool - @dev The contract adds liquidity based on a change in balance of coins. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `add_liquidity_on_rebase`. - The method is non-payable: does not accept native token. @param _amounts List of amounts of coins to deposit @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" - return self._add_liquidity( - msg.sender, - _amounts, - _min_mint_amount, - _use_eth, - _receiver, - 0, - True, # <------------------------------ Expects optimistic transfers. - ) + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + coins: address[N_COINS] = self.coins + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + + # -------------------------- Do Transfers In ----------------------------- + + for i in range(N_COINS): + + if _amounts[i] > 0: + + if coins[i] == WETH20: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + ) + + else: + + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Add incoming balance + self._increase_balances(new_balances) + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[_receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), _receiver, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount @external @@ -905,12 +940,10 @@ def withdraw_admin_fees(): @internal def _exchange( sender: address, - mvalue: uint256, i: int128, j: int128, _dx: uint256, _min_dy: uint256, - use_eth: bool, receiver: address, callbacker: address, callback_sig: bytes32, @@ -941,9 +974,13 @@ def _exchange( # `dx` is whatever the pool received after ERC20 transfer: dx = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth + coins[i], + _dx, + _min_dy, + callbacker, + callback_sig, + sender, + receiver ) # Update stored balances @@ -989,137 +1026,131 @@ def _exchange( @internal -def _add_liquidity( +def _exchange_underlying( sender: address, - amounts: uint256[N_COINS], - min_mint_amount: uint256, - use_eth: bool, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, receiver: address, - mvalue: uint256, - expect_optimistic_transfer: bool = False, + callbacker: address, + callback_sig: bytes32, + expect_optimistic_transfer: bool = False ) -> uint256: - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() rates: uint256[N_COINS] = self._stored_rates() - coins: address[N_COINS] = self.coins - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - - # -------------------------- Do Transfers In ----------------------------- + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - if expect_optimistic_transfer: + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + input_coin: address = empty(address) + output_coin: address = empty(address) - dx: uint256 = 0 + if i == 0: + input_coin = self.coins[0] + else: + base_i = i - MAX_COIN # if i == 1, this reverts + meta_i = 1 + input_coin = BASE_COINS[base_i] + if j == 0: + output_coin = self.coins[0] + else: + base_j = j - MAX_COIN # if j == 1, this reverts + meta_j = 1 + output_coin = BASE_COINS[base_j] - for i in range(N_COINS): + # --------------------------- Do Transfer in ----------------------------- - if amounts[i] > 0: + dx_w_fee: uint256 = 0 - # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] + if expect_optimistic_transfer: - assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" + # This branch is never reached for rebasing tokens + if input_coin == BASE_COINS[base_i]: + # we expect base_coin's balance to be 0. So swap whatever base_coin's + # balance the pool has: + dx_w_fee = ERC20(input_coin).balanceOf(self) + else: + dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] + assert dx_w_fee == _dx + self.stored_balances[meta_i] += dx_w_fee - new_balances[i] += dx + else: - else: + dx_w_fee = self._transfer_in( + input_coin, + _dx, + _min_dy, + callbacker, + callback_sig, + sender, + receiver, + ) - assert total_supply != 0 # dev: initial deposit requires all coins + # ------------------------------------------------------------------------ - else: + if i == 0 or j == 0: - for i in range(N_COINS): + if i == 0: + x = xp[i] + dx_w_fee * rates[i] / PRECISION + else: - if amounts[i] > 0: - - if coins[i] == WETH20: - - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - mvalue, - empty(address), - empty(bytes32), - sender, - empty(address), - use_eth - ) - - else: - - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - sender, - empty(address), - False - ) + dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) + x = dx_w_fee * rates[MAX_COIN] / PRECISION + x += xp[MAX_COIN] - else: + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) - assert total_supply != 0 # dev: initial deposit requires all coins + # Either a real coin or token + dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - # Add incoming balance - self._increase_balances(new_balances) + # Convert all to real units + # Works for both pool coins and real coins + dy = (dy - dy_fee) * PRECISION / rates[meta_j] - # ------------------------------------------------------------------------ + dy_admin_fee: uint256 = dy_fee * ADMIN_FEE / FEE_DENOMINATOR + dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 + self.admin_balances[meta_j] += dy_admin_fee + self.stored_balances[meta_j] -= dy - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount - if total_supply > 0: + assert dy >= _min_dy - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] + # xp is not used anymore, so we reuse it for price calc + xp[meta_i] = x + xp[meta_j] = y + # D is not changed because we did not apply a fee + self.save_p(xp, amp, D) - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) + else: # Swap only involves base pool (user should swap at base pool for better gas) - else: + dy = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy - mint_amount = D1 # Take the dust if there was any + # --------------------------- Do Transfer out ---------------------------- - assert mint_amount >= min_mint_amount, "Slippage screwed you" + assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[receiver] += mint_amount - self.totalSupply = total_supply - log Transfer(empty(address), receiver, mint_amount) + # ------------------------------------------------------------------------ - log AddLiquidity(sender, amounts, fees, D1, total_supply) + log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! - return mint_amount + return dy @internal @@ -1132,14 +1163,11 @@ def _withdraw_admin_fees(): if amounts[i] > 0: - if self.coins[i] == WETH20: - raw_call(receiver, b"", value=amounts[i]) - else: - assert ERC20(self.coins[i]).transfer( - receiver, - amounts[i], - default_return_value=True - ) + assert ERC20(self.coins[i]).transfer( + receiver, + amounts[i], + default_return_value=True + ) self.admin_balances = empty(uint256[N_COINS]) From f2d07eeabcd69f749ea5bcb3589f339668aca82e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:48:06 +0200 Subject: [PATCH 037/337] change name to exchange_received --- contracts/main/CurveStableSwap2NG.vy | 12 ++--- contracts/main/CurveStableSwapFactoryNG.vy | 23 ++++++--- contracts/main/CurveStableSwapMetaNG.vy | 54 ++++++++-------------- 3 files changed, 41 insertions(+), 48 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 28cff0ad..4f333e2c 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -10,7 +10,7 @@ ERC20 tokens can have arbitrary decimals (<=18). Additional features include: 1. Support for rebasing tokens: but this disables - `exchange_with_rebase` and `add_liquidity_with_rebase` + `exchange_received`. 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. 3. Support for ETH/WETH transfers @@ -18,11 +18,11 @@ 5. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature: `exchange_with_rebase`, which is inspired + 6. Adds feature: `exchange_received`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and `IS_REBASING` is True - then calling `exchange_with_rebase` will REVERT. + then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `IS_REBASING` is False then this is an incorrect implementation and rebases can be stolen. @@ -111,7 +111,7 @@ event ApplyNewFee: # ---------------------------- Pool Variables -------------------------------- -WETH20: public(immutable(address)) +WETH20: immutable(address) MAX_POOL_COINS: constant(uint256) = 4 N_COINS: constant(uint256) = 2 @@ -545,7 +545,7 @@ def exchange_extended( @external @nonreentrant('lock') -def exchange_with_rebase( +def exchange_received( i: int128, j: int128, _dx: uint256, @@ -559,7 +559,7 @@ def exchange_with_rebase( dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `exchange_on_rebase`. + directly to the contract and call `exchange_received`. The method is non-payable: does not accept native token. @param i Index value for the coin to send @param j Index valie of the coin to recieve diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 6ee37c03..8d7cb15f 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -627,8 +627,8 @@ def deploy_metapool( _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _method_ids: bytes4[4] = empty(bytes4[4]), - _oracles: address[4] = empty(address[4]), + _method_id: bytes4 = empty(bytes4), + _oracle: address = empty(address), _implementation_idx: uint256 = 0, _is_rebasing: bool = False ) -> address: @@ -652,7 +652,7 @@ def deploy_metapool( @param _implementation_idx Index of the implementation to use. All possible implementations for a BASE_POOL can be publicly accessed via `metapool_implementations(BASE_POOL)` - @param _is_rebasing If coin rebases, then this should be set to True. + @param _is_rebasing If _coin rebases, then this should be set to True. @return Address of the deployed pool """ assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" @@ -665,9 +665,20 @@ def deploy_metapool( decimals: uint256 = ERC20(_coin).decimals() assert decimals < 19, "Max 18 decimals for coins" - # TODO: the following needs an implementaiton contract AND create_from_blueprint - pool: address = create_minimal_proxy_to(implementation) - CurvePool(pool).initialize(_name, _symbol, _coin, 10 ** (36 - decimals), _A, _fee) + pool: address = create_from_blueprint( + implementation, + _name, + _symbol, + _coin, + 10 ** (36 - decimals), # rate multiplier for _coin + _A, + _fee, + _ma_exp_time, + _method_id, + _oracle, + _is_rebasing, + code_offset=3 + ) ERC20(_coin).approve(pool, max_value(uint256)) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b53a55c5..9b89c874 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -16,9 +16,8 @@ of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. 4. Adds `get_dx_underlying`. - 5. Adds `exchange_with_rebase` (expects ERC20 token transferred in and just swaps.) + 5. Adds `exchange_received` (expects ERC20 token transferred in and just swaps.) 6. Adds `exchange_extended` (swap with callback) - 6. Adds `add_liquidity_with_rebase` """ from vyper.interfaces import ERC20 @@ -125,15 +124,13 @@ event ApplyNewFee: # ---------------------------- Pool Variables -------------------------------- -WETH20: public(immutable(address)) - MAX_POOL_COINS: constant(uint256) = 4 N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 MAX_COIN: constant(int128) = N_COINS - 1 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: public(immutable(bool)) +IS_REBASING: immutable(bool) BASE_POOL: immutable(address) BASE_N_COINS: immutable(uint256) @@ -211,7 +208,6 @@ def __init__( _rate_multiplier: uint256, _A: uint256, _fee: uint256, - _weth: address, _ma_exp_time: uint256, _method_id: bytes4, _oracle: address, @@ -245,8 +241,6 @@ def __init__( @param _base_lp_token Address of the basepool's lp token. @param _base_coins Addresses of coins in the base pool. """ - - WETH20 = _weth IS_REBASING = _is_rebasing name = _name @@ -522,7 +516,7 @@ def exchange_extended( @external @nonreentrant('lock') -def exchange_with_rebase( +def exchange_received( i: int128, j: int128, _dx: uint256, @@ -536,7 +530,7 @@ def exchange_with_rebase( dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `exchange_on_rebase`. + directly to the contract and call `exchane_received`. The method is non-payable: does not accept native token. @param i Index value for the coin to send @param j Index valie of the coin to recieve @@ -631,7 +625,7 @@ def exchange_underlying_extended( @external @nonreentrant('lock') -def exchange_underlying_with_rebase( +def exchange_underlying_received( i: int128, j: int128, _dx: uint256, @@ -696,29 +690,15 @@ def add_liquidity( if _amounts[i] > 0: - if coins[i] == WETH20: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - ) - - else: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - ) + new_balances[i] += self._transfer_in( + coins[i], + _amounts[i], + 0, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + ) else: @@ -1094,10 +1074,12 @@ def _exchange_underlying( # ------------------------------------------------------------------------ - if i == 0 or j == 0: + if i == 0 or j == 0: # meta swap if i == 0: + x = xp[i] + dx_w_fee * rates[i] / PRECISION + else: dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) @@ -1136,7 +1118,7 @@ def _exchange_underlying( # D is not changed because we did not apply a fee self.save_p(xp, amp, D) - else: # Swap only involves base pool (user should swap at base pool for better gas) + else: # base pool swap (user should swap at base pool for better gas) dy = ERC20(output_coin).balanceOf(self) Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) From 058b1eee44bc81cd89c72dfb1d860909f2686cb1 Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 29 Jun 2023 23:01:07 +0200 Subject: [PATCH 038/337] fix eip-1271 --- contracts/main/CurveStableSwap2NG.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 2 +- tests/pools/test_token.py | 132 +++++++++++++----------- 3 files changed, 75 insertions(+), 61 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 43a509f6..bc7349e4 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -183,7 +183,7 @@ nonces: public(HashMap[address, uint256]) # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") VERSION_HASH: constant(bytes32) = keccak256(version) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 8b82528a..25d23f9a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -165,7 +165,7 @@ nonces: public(HashMap[address, uint256]) # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") VERSION_HASH: constant(bytes32) = keccak256(version) diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py index 6bc386a4..7d048124 100644 --- a/tests/pools/test_token.py +++ b/tests/pools/test_token.py @@ -1,11 +1,10 @@ import boa import pytest from eip712.messages import EIP712Message +from eth_account._utils.signing import to_bytes32 from tests.utils.transactions import call_returning_result_and_logs -# from eth_account._utils.signing import to_bytes32 - class TestPoolToken: class TestTokenApprove: @@ -67,6 +66,7 @@ class Permit(EIP712Message): _version_: "string" = swap.version() # noqa: F821 _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 _verifyingContract_: "address" = swap.address # noqa: F821 + _salt_: "bytes32" = swap.salt() # noqa: F821 # EIP-2612 Data Fields owner: "address" # noqa: F821 @@ -77,60 +77,74 @@ class Permit(EIP712Message): return Permit - # def test_permit(self, eth_acc, bob, swap): - # value = 2**256 - 1 - # permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) - # sig = eth_acc.sign_message(permit.signable_message) - # - # from eth_account import Account - # - # print(Account.recover_message(permit.signable_message, vrs=(sig.v, sig.r, sig.s))) - # - # swap.permit( - # eth_acc.address, - # bob, - # 2**256 - 1, - # 2**256 - 1, - # sig.v, - # to_bytes32(sig.r), - # to_bytes32(sig.s), - # sender=bob, - # ) - # - # res, events = call_returning_result_and_logs( - # swap, - # "permit", - # eth_acc.address, - # bob, - # 2**256 - 1, - # 2**256 - 1, - # sig.v, - # to_bytes32(sig.r), - # to_bytes32(sig.s), - # sender=bob, - # ) - # - # assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 - # assert res is True - # assert len(events) == 1 - # assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" - # assert swap.nonces(eth_acc.address) == 1 - # - # def test_permit_contract(self, accounts, eth_acc, bob, swap): - # src = """ - # @view - # @external - # def isValidSignature(_hash: bytes32, _sig: Bytes[65]) -> bytes32: - # return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 - # """ - # mock_contract = boa.vyper.deploy(src, sender=bob) - # - # permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=2**256 - 1, nonce=0) - # sig = eth_acc.sign_message(permit.signable_message) - # - # res, events = call_returning_result_and_logs( - # swap, "permit", mock_contract.address, bob, 2**256 - 1, 2**256 - 1, sig.v, sig.r, sig.s, sender=bob - # ) - # assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 - # assert res is True - # assert len(events) == 1 + def test_permit(self, eth_acc, bob, swap): + value = 2**256 - 1 + permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) + sig = eth_acc.sign_message(permit.signable_message) + + res, events = call_returning_result_and_logs( + swap, + "permit", + eth_acc.address, + bob, + 2**256 - 1, + 2**256 - 1, + sig.v, + to_bytes32(sig.r), + to_bytes32(sig.s), + sender=bob, + ) + + assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 + assert res is True + assert len(events) == 1 + assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" + assert swap.nonces(eth_acc.address) == 1 + + def test_permit_contract(self, eth_acc, bob, swap): + # based on https://eips.ethereum.org/EIPS/eip-1271 + src = """ + # @version 0.3.9 + OWNER: public(immutable(address)) + + @external + def __init__(): + OWNER = msg.sender + + @view + @external + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: + signer: address = self._recover_signer(_hash, _signature) + if signer == OWNER: + return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 + return 0xffffffff00000000000000000000000000000000000000000000000000000000 + + @view + @internal + def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: + v: uint256 = convert(slice(_signature, 64, 1), uint256) + r: uint256 = convert(slice(_signature, 0, 32), uint256) + s: uint256 = convert(slice(_signature, 32, 32), uint256) + return ecrecover(_hash, v, r, s) + """ + with boa.env.prank(eth_acc.address): + mock_contract = boa.loads(src) + + permit = self.permit_class(swap)(owner=mock_contract.address, spender=bob, value=2**256 - 1, nonce=0) + sig = eth_acc.sign_message(permit.signable_message) + + res, events = call_returning_result_and_logs( + swap, + "permit", + mock_contract.address, + bob, + 2**256 - 1, + 2**256 - 1, + sig.v, + to_bytes32(sig.r), + to_bytes32(sig.s), + sender=bob, + ) + assert swap.allowance(mock_contract.address, bob) == 2**256 - 1 + assert res is True + assert len(events) == 1 From 2938ecfbe9208c7c53a42610dbb49935ab4b575c Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 30 Jun 2023 01:17:13 +0200 Subject: [PATCH 039/337] add token tests --- tests/conftest.py | 11 +- tests/pools/test_token.py | 246 +++++++++++++++++++++++++++++++++++++- 2 files changed, 251 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c688f60e..789afd43 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,10 +53,11 @@ def pytest_addoption(parser): def pytest_generate_tests(metafunc): pool_size = int(metafunc.config.getoption("pool_size")) - if "pool_size" in metafunc.fixturenames: - # TODO: remove after adding implementations - assert pool_size == 2, "Only 2-coin pools supported" + # TODO: remove after adding implementations + assert pool_size == 2, "Only 2-coin pools supported" + + if "pool_size" in metafunc.fixturenames: metafunc.parametrize( "pool_size", [pool_size], @@ -86,7 +87,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize( "pool_token_types", - [(token_types[v[0]], token_types[v[1]]) for v in combs], + [(token_types[c[0]], token_types[c[1]]) for c in combs], indirect=True, ids=[f"(PoolTokenTypes={c})" for c in combs], ) @@ -139,7 +140,7 @@ def initial_decimals(request): @pytest.fixture(scope="session") def decimals(initial_decimals, pool_token_types): - # eth and oracle pools are always 18 + # eth and oracle tokens are always 18 decimals return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py index 7d048124..5c2c2987 100644 --- a/tests/pools/test_token.py +++ b/tests/pools/test_token.py @@ -5,6 +5,8 @@ from tests.utils.transactions import call_returning_result_and_logs +added_liquidity = pytest.mark.usefixtures("add_initial_liquidity_alice") + class TestPoolToken: class TestTokenApprove: @@ -51,7 +53,7 @@ def test_approval_event_fires(self, alice, bob, swap): assert len(events) == 1 assert repr(events[0]) == f"Approval(owner={alice}, spender={bob}, value={value})" - @pytest.mark.usefixtures("add_initial_liquidity_alice") + @added_liquidity def test_infinite_approval(self, swap, alice, bob): swap.approve(bob, 2**256 - 1, sender=alice) swap.transferFrom(alice, bob, 10**18, sender=bob) @@ -148,3 +150,245 @@ def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: assert swap.allowance(mock_contract.address, bob) == 2**256 - 1 assert res is True assert len(events) == 1 + + class TestTokenTransfer: + @added_liquidity + def test_sender_balance_decreases(self, alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance - amount + + @added_liquidity + def test_receiver_balance_increases(self, alice, bob, swap): + receiver_balance = swap.balanceOf(bob) + amount = swap.balanceOf(alice) // 4 + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(bob) == receiver_balance + amount + + @added_liquidity + def test_total_supply_not_affected(self, alice, bob, swap): + total_supply = swap.totalSupply() + amount = swap.balanceOf(alice) + + swap.transfer(bob, amount, sender=alice) + + assert swap.totalSupply() == total_supply + + @added_liquidity + def test_returns_true(self, alice, bob, swap): + amount = swap.balanceOf(alice) + res = swap.transfer(bob, amount, sender=alice) + + assert res is True + + @added_liquidity + def test_transfer_full_balance(self, alice, bob, swap): + amount = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(bob) + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(alice) == 0 + assert swap.balanceOf(bob) == receiver_balance + amount + + @added_liquidity + def test_transfer_zero_tokens(self, alice, bob, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(bob) + + swap.transfer(bob, 0, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(bob) == receiver_balance + + @added_liquidity + def test_transfer_to_self(self, alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.transfer(alice, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + + @added_liquidity + def test_insufficient_balance(self, alice, bob, swap): + balance = swap.balanceOf(alice) + + with boa.reverts(): + swap.transfer(bob, balance + 1, sender=alice) + + @added_liquidity + def test_transfer_event_fires(self, alice, bob, swap): + amount = swap.balanceOf(alice) + _, events = call_returning_result_and_logs(swap, "transfer", bob, amount, sender=alice) + + assert len(events) == 1 + assert repr(events[0]) == f"Transfer(sender={alice}, receiver={bob}, value={amount})" + + class TestTokenTransferFrom: + @added_liquidity + def test_sender_balance_decreases(self, alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(alice) == sender_balance - amount + + @added_liquidity + def test_receiver_balance_increases(self, alice, bob, charlie, swap): + receiver_balance = swap.balanceOf(charlie) + amount = swap.balanceOf(alice) // 4 + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(charlie) == receiver_balance + amount + + @added_liquidity + def test_caller_balance_not_affected(self, alice, bob, charlie, swap): + caller_balance = swap.balanceOf(bob) + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(bob) == caller_balance + + @added_liquidity + def test_caller_approval_affected(self, alice, bob, charlie, swap): + approval_amount = swap.balanceOf(alice) + transfer_amount = approval_amount // 4 + + swap.approve(bob, approval_amount, sender=alice) + swap.transferFrom(alice, charlie, transfer_amount, sender=bob) + + assert swap.allowance(alice, bob) == approval_amount - transfer_amount + + @added_liquidity + def test_receiver_approval_not_affected(self, alice, bob, charlie, swap): + approval_amount = swap.balanceOf(alice) + transfer_amount = approval_amount // 4 + + swap.approve(bob, approval_amount, sender=alice) + swap.approve(charlie, approval_amount, sender=alice) + swap.transferFrom(alice, charlie, transfer_amount, sender=bob) + + assert swap.allowance(alice, charlie) == approval_amount + + @added_liquidity + def test_total_supply_not_affected(self, alice, bob, charlie, swap): + total_supply = swap.totalSupply() + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.totalSupply() == total_supply + + @added_liquidity + def test_returns_true(self, alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + swap.approve(bob, amount, sender=alice) + res = swap.transferFrom(alice, charlie, amount, sender=bob) + + assert res is True + + @added_liquidity + def test_transfer_full_balance(self, alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(alice) == 0 + assert swap.balanceOf(charlie) == receiver_balance + amount + + @added_liquidity + def test_transfer_zero_tokens(self, alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.approve(bob, sender_balance, sender=alice) + swap.transferFrom(alice, charlie, 0, sender=bob) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(charlie) == receiver_balance + + @added_liquidity + def test_transfer_zero_tokens_without_approval(self, alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.transferFrom(alice, charlie, 0, sender=bob) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(charlie) == receiver_balance + + @added_liquidity + def test_insufficient_balance(self, alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance + 1, sender=alice) + with boa.reverts(): + swap.transferFrom(alice, charlie, balance + 1, sender=bob) + + @added_liquidity + def test_insufficient_approval(self, alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance - 1, sender=alice) + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + @added_liquidity + def test_no_approval(self, alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + @added_liquidity + def test_revoked_approval(self, alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance, sender=alice) + swap.approve(bob, 0, sender=alice) + + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + @added_liquidity + def test_transfer_to_self(self, alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.approve(alice, sender_balance, sender=alice) + swap.transferFrom(alice, alice, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + assert swap.allowance(alice, alice) == sender_balance - amount + + @added_liquidity + def test_transfer_to_self_no_approval(self, alice, bob, swap): + amount = swap.balanceOf(alice) + + with boa.reverts(): + swap.transferFrom(alice, alice, amount, sender=alice) + + @added_liquidity + def test_transfer_event_fires(self, alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + _, events = call_returning_result_and_logs(swap, "transferFrom", alice, charlie, amount, sender=bob) + + assert len(events) == 1 + assert repr(events[0]) == f"Transfer(sender={alice}, receiver={charlie}, value={amount})" From b14e77178b9ddad4a6bcd98f47fae457cabacac8 Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 30 Jun 2023 02:02:00 +0200 Subject: [PATCH 040/337] add skip by tokens and/or pool type --- tests/conftest.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 789afd43..7ecd3a25 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -144,6 +144,33 @@ def decimals(initial_decimals, pool_token_types): return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] +# Usage +# @pytest.mark.only_for_token_types(1,2) +# +# will not be skipped only if at least one of tokens in pool is eth or oracle +# can be applied to classes +# +# @pytest.mark.only_for_token_types(2) +# class TestPoolsWithOracleToken: +@pytest.fixture(autouse=True) +def skip_by_token_type(request, pool_token_types): + only_for_token_types = request.node.get_closest_marker("only_for_token_types") + if only_for_token_types: + if not any(pool_token_type in only_for_token_types.args for pool_token_type in pool_token_types): + pytest.skip("skipped because no tokens for these types") + + +# Usage +# @pytest.mark.only_for_pool_type(1) +# class TestMetaPool... +@pytest.fixture(autouse=True) +def skip_by_pool_type(request, pool_type): + only_for_pool_type = request.node.get_closest_marker("only_for_pool_type") + if only_for_pool_type: + if pool_type not in only_for_pool_type.args: + pytest.skip("skipped because another pool type") + + @pytest.fixture(scope="module") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") From be5cc5ee9b9319a97e3be8ea62f0392d4a6efc5d Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 30 Jun 2023 02:09:17 +0200 Subject: [PATCH 041/337] fix fixtures - use in class --- tests/pools/test_token.py | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/tests/pools/test_token.py b/tests/pools/test_token.py index 5c2c2987..3c9ca6d3 100644 --- a/tests/pools/test_token.py +++ b/tests/pools/test_token.py @@ -151,8 +151,8 @@ def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: assert res is True assert len(events) == 1 + @added_liquidity class TestTokenTransfer: - @added_liquidity def test_sender_balance_decreases(self, alice, bob, swap): sender_balance = swap.balanceOf(alice) amount = sender_balance // 4 @@ -161,7 +161,6 @@ def test_sender_balance_decreases(self, alice, bob, swap): assert swap.balanceOf(alice) == sender_balance - amount - @added_liquidity def test_receiver_balance_increases(self, alice, bob, swap): receiver_balance = swap.balanceOf(bob) amount = swap.balanceOf(alice) // 4 @@ -170,7 +169,6 @@ def test_receiver_balance_increases(self, alice, bob, swap): assert swap.balanceOf(bob) == receiver_balance + amount - @added_liquidity def test_total_supply_not_affected(self, alice, bob, swap): total_supply = swap.totalSupply() amount = swap.balanceOf(alice) @@ -179,14 +177,12 @@ def test_total_supply_not_affected(self, alice, bob, swap): assert swap.totalSupply() == total_supply - @added_liquidity def test_returns_true(self, alice, bob, swap): amount = swap.balanceOf(alice) res = swap.transfer(bob, amount, sender=alice) assert res is True - @added_liquidity def test_transfer_full_balance(self, alice, bob, swap): amount = swap.balanceOf(alice) receiver_balance = swap.balanceOf(bob) @@ -196,7 +192,6 @@ def test_transfer_full_balance(self, alice, bob, swap): assert swap.balanceOf(alice) == 0 assert swap.balanceOf(bob) == receiver_balance + amount - @added_liquidity def test_transfer_zero_tokens(self, alice, bob, swap): sender_balance = swap.balanceOf(alice) receiver_balance = swap.balanceOf(bob) @@ -206,7 +201,6 @@ def test_transfer_zero_tokens(self, alice, bob, swap): assert swap.balanceOf(alice) == sender_balance assert swap.balanceOf(bob) == receiver_balance - @added_liquidity def test_transfer_to_self(self, alice, bob, swap): sender_balance = swap.balanceOf(alice) amount = sender_balance // 4 @@ -215,14 +209,12 @@ def test_transfer_to_self(self, alice, bob, swap): assert swap.balanceOf(alice) == sender_balance - @added_liquidity def test_insufficient_balance(self, alice, bob, swap): balance = swap.balanceOf(alice) with boa.reverts(): swap.transfer(bob, balance + 1, sender=alice) - @added_liquidity def test_transfer_event_fires(self, alice, bob, swap): amount = swap.balanceOf(alice) _, events = call_returning_result_and_logs(swap, "transfer", bob, amount, sender=alice) @@ -230,8 +222,8 @@ def test_transfer_event_fires(self, alice, bob, swap): assert len(events) == 1 assert repr(events[0]) == f"Transfer(sender={alice}, receiver={bob}, value={amount})" + @added_liquidity class TestTokenTransferFrom: - @added_liquidity def test_sender_balance_decreases(self, alice, bob, charlie, swap): sender_balance = swap.balanceOf(alice) amount = sender_balance // 4 @@ -241,7 +233,6 @@ def test_sender_balance_decreases(self, alice, bob, charlie, swap): assert swap.balanceOf(alice) == sender_balance - amount - @added_liquidity def test_receiver_balance_increases(self, alice, bob, charlie, swap): receiver_balance = swap.balanceOf(charlie) amount = swap.balanceOf(alice) // 4 @@ -251,7 +242,6 @@ def test_receiver_balance_increases(self, alice, bob, charlie, swap): assert swap.balanceOf(charlie) == receiver_balance + amount - @added_liquidity def test_caller_balance_not_affected(self, alice, bob, charlie, swap): caller_balance = swap.balanceOf(bob) amount = swap.balanceOf(alice) @@ -261,7 +251,6 @@ def test_caller_balance_not_affected(self, alice, bob, charlie, swap): assert swap.balanceOf(bob) == caller_balance - @added_liquidity def test_caller_approval_affected(self, alice, bob, charlie, swap): approval_amount = swap.balanceOf(alice) transfer_amount = approval_amount // 4 @@ -271,7 +260,6 @@ def test_caller_approval_affected(self, alice, bob, charlie, swap): assert swap.allowance(alice, bob) == approval_amount - transfer_amount - @added_liquidity def test_receiver_approval_not_affected(self, alice, bob, charlie, swap): approval_amount = swap.balanceOf(alice) transfer_amount = approval_amount // 4 @@ -282,7 +270,6 @@ def test_receiver_approval_not_affected(self, alice, bob, charlie, swap): assert swap.allowance(alice, charlie) == approval_amount - @added_liquidity def test_total_supply_not_affected(self, alice, bob, charlie, swap): total_supply = swap.totalSupply() amount = swap.balanceOf(alice) @@ -292,7 +279,6 @@ def test_total_supply_not_affected(self, alice, bob, charlie, swap): assert swap.totalSupply() == total_supply - @added_liquidity def test_returns_true(self, alice, bob, charlie, swap): amount = swap.balanceOf(alice) swap.approve(bob, amount, sender=alice) @@ -300,7 +286,6 @@ def test_returns_true(self, alice, bob, charlie, swap): assert res is True - @added_liquidity def test_transfer_full_balance(self, alice, bob, charlie, swap): amount = swap.balanceOf(alice) receiver_balance = swap.balanceOf(charlie) @@ -311,7 +296,6 @@ def test_transfer_full_balance(self, alice, bob, charlie, swap): assert swap.balanceOf(alice) == 0 assert swap.balanceOf(charlie) == receiver_balance + amount - @added_liquidity def test_transfer_zero_tokens(self, alice, bob, charlie, swap): sender_balance = swap.balanceOf(alice) receiver_balance = swap.balanceOf(charlie) @@ -322,7 +306,6 @@ def test_transfer_zero_tokens(self, alice, bob, charlie, swap): assert swap.balanceOf(alice) == sender_balance assert swap.balanceOf(charlie) == receiver_balance - @added_liquidity def test_transfer_zero_tokens_without_approval(self, alice, bob, charlie, swap): sender_balance = swap.balanceOf(alice) receiver_balance = swap.balanceOf(charlie) @@ -332,7 +315,6 @@ def test_transfer_zero_tokens_without_approval(self, alice, bob, charlie, swap): assert swap.balanceOf(alice) == sender_balance assert swap.balanceOf(charlie) == receiver_balance - @added_liquidity def test_insufficient_balance(self, alice, bob, charlie, swap): balance = swap.balanceOf(alice) @@ -340,7 +322,6 @@ def test_insufficient_balance(self, alice, bob, charlie, swap): with boa.reverts(): swap.transferFrom(alice, charlie, balance + 1, sender=bob) - @added_liquidity def test_insufficient_approval(self, alice, bob, charlie, swap): balance = swap.balanceOf(alice) @@ -348,14 +329,12 @@ def test_insufficient_approval(self, alice, bob, charlie, swap): with boa.reverts(): swap.transferFrom(alice, charlie, balance, sender=bob) - @added_liquidity def test_no_approval(self, alice, bob, charlie, swap): balance = swap.balanceOf(alice) with boa.reverts(): swap.transferFrom(alice, charlie, balance, sender=bob) - @added_liquidity def test_revoked_approval(self, alice, bob, charlie, swap): balance = swap.balanceOf(alice) @@ -365,7 +344,6 @@ def test_revoked_approval(self, alice, bob, charlie, swap): with boa.reverts(): swap.transferFrom(alice, charlie, balance, sender=bob) - @added_liquidity def test_transfer_to_self(self, alice, bob, swap): sender_balance = swap.balanceOf(alice) amount = sender_balance // 4 @@ -376,14 +354,12 @@ def test_transfer_to_self(self, alice, bob, swap): assert swap.balanceOf(alice) == sender_balance assert swap.allowance(alice, alice) == sender_balance - amount - @added_liquidity def test_transfer_to_self_no_approval(self, alice, bob, swap): amount = swap.balanceOf(alice) with boa.reverts(): swap.transferFrom(alice, alice, amount, sender=alice) - @added_liquidity def test_transfer_event_fires(self, alice, bob, charlie, swap): amount = swap.balanceOf(alice) From ba84611b7482c05ec74538bf625a5e32e48bb8b1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:39:23 +0200 Subject: [PATCH 042/337] support coins up to 8 (wip) --- contracts/main/CurveStableSwap2NG.vy | 18 ++-- contracts/main/CurveStableSwapFactoryNG.vy | 110 +++++++++++---------- contracts/main/CurveStableSwapMetaNG.vy | 27 +++-- 3 files changed, 86 insertions(+), 69 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 4f333e2c..e4f78662 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -109,15 +109,15 @@ event ApplyNewFee: fee: uint256 +MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory + # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) -MAX_POOL_COINS: constant(uint256) = 4 - N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(bool[MAX_POOL_COINS]) +IS_REBASING: immutable(bool[N_COINS]) factory: public(address) coins: public(address[N_COINS]) @@ -187,15 +187,15 @@ CACHED_DOMAIN_SEPARATOR: immutable(bytes32) def __init__( _name: String[32], _symbol: String[10], - _coins: address[MAX_POOL_COINS], - _rate_multipliers: uint256[MAX_POOL_COINS], + _coins: address[MAX_COINS], + _rate_multipliers: uint256[MAX_COINS], _A: uint256, _fee: uint256, _weth: address, _ma_exp_time: uint256, - _method_ids: bytes4[MAX_POOL_COINS], - _oracles: address[MAX_POOL_COINS], - _is_rebasing: bool[MAX_POOL_COINS] + _method_ids: bytes4[MAX_COINS], + _oracles: address[MAX_COINS], + _is_rebasing: bool[MAX_COINS] ): """ @notice Initialize the pool contract @@ -222,7 +222,7 @@ def __init__( """ WETH20 = _weth - IS_REBASING = _is_rebasing + IS_REBASING = [_is_rebasing[0], _is_rebasing[1]] name = _name symbol = _symbol diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 8d7cb15f..93d0125c 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -10,8 +10,8 @@ struct PoolArray: base_pool: address implementation: address liquidity_gauge: address - coins: address[MAX_PLAIN_COINS] - decimals: uint256[MAX_PLAIN_COINS] + coins: address[MAX_COINS] + decimals: uint256[MAX_COINS] n_coins: uint256 asset_type: uint256 @@ -20,6 +20,7 @@ struct BasePoolArray: lp_token: address fee_receiver: address coins: address[MAX_COINS] + is_rebasing: bool[MAX_COINS] decimals: uint256 n_coins: uint256 asset_type: uint256 @@ -27,13 +28,6 @@ struct BasePoolArray: interface AddressProvider: def admin() -> address: view - def get_registry() -> address: view - -interface Registry: - def get_lp_token(pool: address) -> address: view - def get_n_coins(pool: address) -> uint256: view - def get_coins(pool: address) -> address[MAX_COINS]: view - def get_pool_from_lp_token(lp_token: address) -> address: view interface ERC20: def balanceOf(_addr: address) -> uint256: view @@ -56,7 +50,7 @@ interface CurvePool: _A: uint256, _fee: uint256, ): nonpayable - def exchange( + def exchange( # TODO: change this! i: int128, j: int128, dx: uint256, @@ -68,18 +62,12 @@ interface CurveFactoryMetapool: def coins(i :uint256) -> address: view def decimals() -> uint256: view -interface OldFactory: - def get_coins(_pool: address) -> address[2]: view - -interface LiquidityGauge: - def initialize(_lp_token: address): nonpayable - event BasePoolAdded: base_pool: address event PlainPoolDeployed: - coins: address[MAX_PLAIN_COINS] + coins: address[MAX_COINS] A: uint256 fee: uint256 deployer: address @@ -98,7 +86,6 @@ event LiquidityGaugeDeployed: WETH20: public(immutable(address)) MAX_COINS: constant(uint256) = 8 -MAX_PLAIN_COINS: constant(uint256) = 4 # max coins in a plain pool ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 OLD_FACTORY: constant(address) = 0x0959158b6040D32d04c301A72CBFD6b39E21c9AE @@ -111,7 +98,7 @@ pool_data: HashMap[address, PoolArray] base_pool_list: public(address[4294967296]) # master list of pools base_pool_count: public(uint256) # actual length of pool_list -base_pool_data: HashMap[address, BasePoolArray] +base_pool_data: public(HashMap[address, BasePoolArray]) # asset -> is used in a metapool? base_pool_assets: public(HashMap[address, bool]) @@ -209,7 +196,7 @@ def get_meta_n_coins(_pool: address) -> (uint256, uint256): @view @external -def get_coins(_pool: address) -> address[MAX_PLAIN_COINS]: +def get_coins(_pool: address) -> address[MAX_COINS]: """ @notice Get the coins within a pool @param _pool Pool address @@ -241,14 +228,14 @@ def get_underlying_coins(_pool: address) -> address[MAX_COINS]: @view @external -def get_decimals(_pool: address) -> uint256[MAX_PLAIN_COINS]: +def get_decimals(_pool: address) -> uint256[MAX_COINS]: """ @notice Get decimal places for each coin within a pool @param _pool Pool address @return uint256 list of decimals """ if self.pool_data[_pool].base_pool != empty(address): - decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) decimals = self.pool_data[_pool].decimals decimals[1] = 18 return decimals @@ -265,7 +252,7 @@ def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: """ # decimals are tightly packed as a series of uint8 within a little-endian bytes32 # the packed value is stored as uint256 to simplify unpacking via shift and modulo - pool_decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + pool_decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) pool_decimals = self.pool_data[_pool].decimals decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) decimals[0] = pool_decimals[0] @@ -297,22 +284,27 @@ def get_metapool_rates(_pool: address) -> uint256[2]: @view @external -def get_balances(_pool: address) -> uint256[MAX_PLAIN_COINS]: +def get_balances(_pool: address) -> uint256[MAX_COINS]: """ @notice Get balances for each coin within a pool @dev For pools using lending, these are the wrapped coin balances @param _pool Pool address @return uint256 list of balances """ + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + if self.pool_data[_pool].base_pool != empty(address): - return [CurvePool(_pool).balances(0), CurvePool(_pool).balances(1), 0, 0] + balances[0] = CurvePool(_pool).balances(0) + balances[1] = CurvePool(_pool).balances(1) + return balances + n_coins: uint256 = self.pool_data[_pool].n_coins - balances: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) - for i in range(MAX_PLAIN_COINS): + for i in range(MAX_COINS): if i < n_coins: balances[i] = CurvePool(_pool).balances(i) else: balances[i] = 0 + return balances @@ -366,15 +358,15 @@ def get_fees(_pool: address) -> (uint256, uint256): @view @external -def get_admin_balances(_pool: address) -> uint256[MAX_PLAIN_COINS]: +def get_admin_balances(_pool: address) -> uint256[MAX_COINS]: """ @notice Get the current admin balances (uncollected fees) for a pool @param _pool Pool address @return List of uint256 admin balances """ n_coins: uint256 = self.pool_data[_pool].n_coins - admin_balances: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) - for i in range(MAX_PLAIN_COINS): + admin_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + for i in range(MAX_COINS): if i == n_coins: break admin_balances[i] = CurvePool(_pool).admin_balances(i) @@ -409,7 +401,7 @@ def get_coin_indices( j: uint256 = 0 for x in range(MAX_COINS): if base_pool == empty(address): - if x >= MAX_PLAIN_COINS: + if x >= MAX_COINS: raise "No available market" if x != 0: coin = self.pool_data[_pool].coins[x] @@ -499,12 +491,12 @@ def get_fee_receiver(_pool: address) -> address: def deploy_plain_pool( _name: String[32], _symbol: String[10], - _coins: address[MAX_PLAIN_COINS], + _coins: address[MAX_COINS], _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _method_ids: bytes4[4] = empty(bytes4[4]), - _oracles: address[4] = empty(address[4]), + _method_ids: bytes4[MAX_COINS] = empty(bytes4[MAX_COINS]), + _oracles: address[MAX_COINS] = empty(address[MAX_COINS]), _asset_type: uint256 = 0, _implementation_idx: uint256 = 0, _is_rebasing: bool = False @@ -540,11 +532,11 @@ def deploy_plain_pool( """ assert _fee <= 100000000, "Invalid fee" - n_coins: uint256 = MAX_PLAIN_COINS - rate_multipliers: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) - decimals: uint256[MAX_PLAIN_COINS] = empty(uint256[MAX_PLAIN_COINS]) + n_coins: uint256 = MAX_COINS + rate_multipliers: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - for i in range(MAX_PLAIN_COINS): + for i in range(MAX_COINS): coin: address = _coins[i] @@ -558,8 +550,8 @@ def deploy_plain_pool( rate_multipliers[i] = 10 ** (36 - decimals[i]) - for x in range(i, i+MAX_PLAIN_COINS): - if x+1 == MAX_PLAIN_COINS: + for x in range(i, i + MAX_COINS): + if x+1 == MAX_COINS: break if _coins[x+1] == empty(address): break @@ -593,7 +585,7 @@ def deploy_plain_pool( if _asset_type != 0: self.pool_data[pool].asset_type = _asset_type - for i in range(MAX_PLAIN_COINS): + for i in range(MAX_COINS): coin: address = _coins[i] if coin == empty(address): break @@ -606,7 +598,7 @@ def deploy_plain_pool( convert(max_value(uint256), bytes32) ) ) - for j in range(MAX_PLAIN_COINS): + for j in range(MAX_COINS): if i < j: swappable_coin: address = _coins[j] key: uint256 = (convert(coin, uint256) ^ convert(swappable_coin, uint256)) @@ -665,6 +657,17 @@ def deploy_metapool( decimals: uint256 = ERC20(_coin).decimals() assert decimals < 19, "Max 18 decimals for coins" + # combine _coins's _is_rebasing and basepool coins _is_rebasing: + base_pool_is_rebasing: bool[MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing + is_rebasing: bool[MAX_COINS] = empty(bool[MAX_COINS]) + is_rebasing[0] = _is_rebasing + for i in range(MAX_COINS): + + if i+1 == MAX_COINS: + break + + is_rebasing[i+1] = base_pool_is_rebasing[i] + pool: address = create_from_blueprint( implementation, _name, @@ -677,6 +680,9 @@ def deploy_metapool( _method_id, _oracle, _is_rebasing, + _base_pool, + self.base_pool_data[_base_pool].lp_token, + self.base_pool_data[_base_pool].coins, code_offset=3 ) @@ -689,7 +695,7 @@ def deploy_metapool( base_lp_token: address = self.base_pool_data[_base_pool].lp_token - self.pool_data[pool].decimals = [decimals, 0, 0, 0] + self.pool_data[pool].decimals = [decimals, 0, 0, 0, 0, 0, 0, 0] self.pool_data[pool].n_coins = 2 self.pool_data[pool].base_pool = _base_pool self.pool_data[pool].coins[0] = _coin @@ -738,8 +744,12 @@ def deploy_gauge(_pool: address) -> address: @external def add_base_pool( _base_pool: address, + _base_lp_token: address, _fee_receiver: address, + _coins: address[MAX_COINS], _asset_type: uint256, + _n_coins: uint256, + _is_rebasing: bool[MAX_COINS], _implementations: address[10], ): """ @@ -748,20 +758,19 @@ def add_base_pool( @param _base_pool Pool address to add @param _fee_receiver Admin fee receiver address for metapools using this base pool @param _asset_type Asset type for pool, as an integer 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing @param _implementations List of implementation addresses that can be used with this base pool """ assert msg.sender == self.admin # dev: admin-only function assert self.base_pool_data[_base_pool].coins[0] == empty(address) # dev: pool exists - - registry: address = AddressProvider(ADDRESS_PROVIDER).get_registry() - n_coins: uint256 = Registry(registry).get_n_coins(_base_pool) + assert _n_coins < MAX_COINS # dev: base pool can only have (MAX_COINS - 1) coins. # add pool to pool_list length: uint256 = self.base_pool_count self.base_pool_list[length] = _base_pool self.base_pool_count = length + 1 - self.base_pool_data[_base_pool].lp_token = Registry(registry).get_lp_token(_base_pool) - self.base_pool_data[_base_pool].n_coins = n_coins + self.base_pool_data[_base_pool].lp_token = _base_lp_token + self.base_pool_data[_base_pool].n_coins = _n_coins self.base_pool_data[_base_pool].fee_receiver = _fee_receiver if _asset_type != 0: self.base_pool_data[_base_pool].asset_type = _asset_type @@ -773,12 +782,13 @@ def add_base_pool( self.base_pool_data[_base_pool].implementations[i] = implementation decimals: uint256 = 0 - coins: address[MAX_COINS] = Registry(registry).get_coins(_base_pool) + coins: address[MAX_COINS] = _coins for i in range(MAX_COINS): - if i == n_coins: + if i == _n_coins: break coin: address = coins[i] self.base_pool_data[_base_pool].coins[i] = coin + self.base_pool_data[_base_pool].is_rebasing[i] = _is_rebasing[i] self.base_pool_assets[coin] = True decimals += (ERC20(coin).decimals() << i*8) self.base_pool_data[_base_pool].decimals = decimals diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9b89c874..1a584518 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -124,17 +124,19 @@ event ApplyNewFee: # ---------------------------- Pool Variables -------------------------------- -MAX_POOL_COINS: constant(uint256) = 4 +MAX_COINS: constant(uint256) = 8 N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 MAX_COIN: constant(int128) = N_COINS - 1 PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(bool) + +# token: is_rebasing flag +is_rebasing: HashMap[address, bool] BASE_POOL: immutable(address) BASE_N_COINS: immutable(uint256) -BASE_COINS: immutable(address[MAX_POOL_COINS]) +BASE_COINS: immutable(address[MAX_COINS]) factory: public(address) coins: public(address[N_COINS]) @@ -211,10 +213,10 @@ def __init__( _ma_exp_time: uint256, _method_id: bytes4, _oracle: address, - _is_rebasing: bool, + _is_rebasing: bool[MAX_COINS], # [_coin, False, base_coin_0, base_coin_1, ...] _base_pool: address, _base_lp_token: address, - _base_coins: address[MAX_POOL_COINS], + _base_coins: address[MAX_COINS], # base pool can have maximally (MAX_COINS - 1) coins ): """ @notice Initialize the pool contract @@ -241,8 +243,6 @@ def __init__( @param _base_lp_token Address of the basepool's lp token. @param _base_coins Addresses of coins in the base pool. """ - IS_REBASING = _is_rebasing - name = _name symbol = _symbol @@ -253,6 +253,9 @@ def __init__( BASE_COINS = _base_coins BASE_POOL = _base_pool + self.is_rebasing[_coin] = _is_rebasing[0] + self.is_rebasing[_base_lp_token] = False + base_n_coins: uint256 = 0 for coin in _base_coins: @@ -262,7 +265,9 @@ def __init__( ERC20(coin).approve(BASE_POOL, max_value(uint256)) base_n_coins += 1 - assert base_n_coins <= MAX_POOL_COINS, "Cannot onboard base pool with more than 4 coins" + # _is_rebasing from idx 1 and onwards is for base pool's coins + self.is_rebasing[coin] = _is_rebasing[base_n_coins] + BASE_N_COINS = base_n_coins A: uint256 = _A * A_PRECISION @@ -538,7 +543,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" + assert self.is_rebasing[self.coins[i]] # dev: rebasing tokens are not supported return self._exchange( msg.sender, i, @@ -610,6 +615,7 @@ def exchange_underlying_extended( @param _receiver Address that receives `j` @return Actual amount of `j` received """ + assert _cb != empty(bytes32) # dev: no callback specified return self._exchange_underlying( msg.sender, i, @@ -644,7 +650,6 @@ def exchange_underlying_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not IS_REBASING, "Call disabled if IS_REBASING is True" return self._exchange_underlying( msg.sender, i, @@ -1050,6 +1055,8 @@ def _exchange_underlying( if expect_optimistic_transfer: + assert self.is_rebasing[input_coin] # dev: rebasing coins not supported + # This branch is never reached for rebasing tokens if input_coin == BASE_COINS[base_i]: # we expect base_coin's balance to be 0. So swap whatever base_coin's From 68a0f303a854fb393ab70e9fc469ccc7b3bf5ded Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:47:23 +0200 Subject: [PATCH 043/337] fix handler --- .../main/CurveStableSwapFactoryNGHandler.vy | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index caf7d7b0..196c7935 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -9,13 +9,13 @@ # ---- interfaces ---- # interface BaseRegistry: def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address: view - def get_admin_balances(_pool: address) -> uint256[MAX_COINS]: view + def get_admin_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view def get_A(_pool: address) -> uint256: view - def get_balances(_pool: address) -> uint256[MAX_COINS]: view + def get_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view def get_base_pool(_pool: address) -> address: view - def get_coins(_pool: address) -> address[MAX_COINS]: view + def get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view def get_coin_indices(_pool: address, _from: address, _to: address) -> (int128, int128): view - def get_decimals(_pool: address) -> uint256[MAX_COINS]: view + def get_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view def get_fees(_pool: address) -> uint256[2]: view def get_gauge(_pool: address) -> address: view def get_lp_token(_pool: address) -> address: view @@ -23,7 +23,7 @@ interface BaseRegistry: def get_n_coins(_pool: address) -> uint256: view def get_pool_asset_type(_pool: address) -> uint256: view def get_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view - def get_underlying_coins(_pool: address) -> address[MAX_COINS]: view + def get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view def get_underlying_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view def is_meta(_pool: address) -> bool: view def pool_count() -> uint256: view @@ -71,7 +71,6 @@ interface MetaRegistry: # ---- constants ---- # GAUGE_CONTROLLER: constant(address) = 0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB -MAX_COINS: constant(uint256) = 4 MAX_METAREGISTRY_COINS: constant(uint256) = 8 @@ -97,9 +96,9 @@ def _is_meta(_pool: address) -> bool: @internal @view def _get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: - _coins: address[MAX_COINS] = self.base_registry.get_coins(_pool) + _coins: address[MAX_METAREGISTRY_COINS] = self.base_registry.get_coins(_pool) _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) - for i in range(MAX_COINS): + for i in range(MAX_METAREGISTRY_COINS): _padded_coins[i] = _coins[i] return _padded_coins @@ -107,9 +106,9 @@ def _get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: @internal @view def _get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: - _coins: address[MAX_COINS] = self.base_registry.get_underlying_coins(_pool) + _coins: address[MAX_METAREGISTRY_COINS] = self.base_registry.get_underlying_coins(_pool) _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) - for i in range(MAX_COINS): + for i in range(MAX_METAREGISTRY_COINS): _padded_coins[i] = _coins[i] return _padded_coins @@ -170,9 +169,9 @@ def _get_meta_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_CO @internal @view -def _pad_uint_array(_array: uint256[MAX_COINS]) -> uint256[MAX_METAREGISTRY_COINS]: +def _pad_uint_array(_array: uint256[MAX_METAREGISTRY_COINS]) -> uint256[MAX_METAREGISTRY_COINS]: _padded_array: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) - for i in range(MAX_COINS): + for i in range(MAX_METAREGISTRY_COINS): _padded_array[i] = _array[i] return _padded_array From 96e7162446dc77cb683be656aaf18c924152ef41 Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 30 Jun 2023 17:48:55 +0200 Subject: [PATCH 044/337] fix and add factory --- .github/workflows/test_factory.yaml | 44 ++++ contracts/main/CurveStableSwapFactoryNG.vy | 3 +- pyproject.toml | 6 + tests/fixtures/pools.py | 10 +- tests/test_factory.py | 285 +++++++++++++++++++++ 5 files changed, 342 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/test_factory.yaml create mode 100644 tests/test_factory.py diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml new file mode 100644 index 00000000..45aaa196 --- /dev/null +++ b/.github/workflows/test_factory.yaml @@ -0,0 +1,44 @@ +name: test_gauge + +on: + pull_request: + paths: + - "tests/test_factory.py" + - "contracts/CurveStableSwapFactoryNG.vy" + push: + paths: + - "tests/test_factory.py" + - "contracts/CurveStableSwapFactoryNG.vy" + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Cache Compiler Installations + uses: actions/cache@v3 + with: + path: | + ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: | + pip install poetry==1.5.1 + poetry config virtualenvs.in-project true + poetry install --no-interaction + + - name: Run Tests + run: | + source .venv/bin/activate + pytest tests/test_factory.py --pool-size=2 --decimals=18,18 -n auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 93d0125c..8401d55f 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -321,6 +321,7 @@ def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: underlying_balances[0] = CurvePool(_pool).balances(0) base_total_supply: uint256 = ERC20(self.pool_data[_pool].coins[1]).totalSupply() + print(base_total_supply) if base_total_supply > 0: underlying_pct: uint256 = CurvePool(_pool).balances(1) * 10**36 / base_total_supply base_pool: address = self.pool_data[_pool].base_pool @@ -499,7 +500,7 @@ def deploy_plain_pool( _oracles: address[MAX_COINS] = empty(address[MAX_COINS]), _asset_type: uint256 = 0, _implementation_idx: uint256 = 0, - _is_rebasing: bool = False + _is_rebasing: bool[MAX_COINS] = empty(bool[MAX_COINS]) ) -> address: """ @notice Deploy a new plain pool diff --git a/pyproject.toml b/pyproject.toml index 63ff1317..05dfdc2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,3 +58,9 @@ target_version = ['py310'] profile = "black" py_version = 310 known_first_party = "poetry" + +[tool.pytest.ini_options] +markers = [ + "only_for_pool_type: running tests only for specific pool types", + "only_for_token_types: running tests only if tokens of specific types are in pool", +] diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 58796c96..80fe2c81 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -22,10 +22,10 @@ def swap( A = 2000 fee = 1000000 - method_ids = [bytes(b"")] * 4 - oracles = [zero_address] * 4 + method_ids = [bytes(b"")] * 8 + oracles = [zero_address] * 8 asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other - is_rebasing = False + is_rebasing = [False] * 8 for i, t in enumerate(pool_token_types): if t == 0: @@ -46,13 +46,13 @@ def swap( A = 500 fee = 4000000 asset_type = 1 - is_rebasing = True + is_rebasing[i] = True with boa.env.prank(owner): pool = factory.deploy_plain_pool( "test", "test", - [pool_tokens[0].address, pool_tokens[1].address, zero_address, zero_address], + [pool_tokens[0].address, pool_tokens[1].address, *[zero_address] * 6], A, fee, 866, diff --git a/tests/test_factory.py b/tests/test_factory.py new file mode 100644 index 00000000..536e9e76 --- /dev/null +++ b/tests/test_factory.py @@ -0,0 +1,285 @@ +# import itertools + +import boa +import pytest + +MAX_COINS = 8 + + +class TestFactory: + @pytest.fixture + def new_factory(self, alice, fee_receiver, weth): + with boa.env.prank(alice): + return boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + alice, + weth, + ) + + class TestBasic: + @pytest.fixture + def new_factory_setup( + self, + new_factory, + amm_implementation_plain, + pool_size, + alice, + fee_receiver, + ): + with boa.env.prank(alice): + new_factory.set_plain_implementations(pool_size, 0, amm_implementation_plain.address) + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receiving): + assert ( + factory.find_pool_for_coins(pool_tokens[sending].address, pool_tokens[receiving].address) + == swap.address + ) + + def test_get_coins(self, factory, swap, pool_tokens, pool_size, zero_address): + assert factory.get_coins(swap.address) == [pt.address for pt in pool_tokens] + [zero_address] * ( + MAX_COINS - pool_size + ) + + def test_get_decimals(self, factory, swap, decimals): + assert factory.get_decimals(swap.address) == decimals + [0] * (MAX_COINS - len(decimals)) + + def test_get_balances(self, factory, swap, pool_size): + assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] + [0] * ( + MAX_COINS - pool_size + ) + + @pytest.mark.only_for_pool_type(0) + def test_get_underlying_balances(self, factory, swap): + with boa.reverts() as e: + factory.get_underlying_balances(swap.address) + assert str(e) == "dev: pool is not a metapool" + + def test_get_A(self, factory, swap): + assert factory.get_A(swap.address) == swap.A() + + def test_get_fees(self, factory, swap): + assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) + + def test_get_admin_balances(self, factory, swap, pool_size): + assert factory.get_admin_balances(swap.address) == [swap.admin_balances(i) for i in range(pool_size)] + [ + 0 + ] * (MAX_COINS - pool_size) + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_get_coin_indices(self, factory, swap, sending, receiving, pool_tokens): + i, j, is_underlying = factory.get_coin_indices( + swap.address, pool_tokens[sending].address, pool_tokens[receiving].address + ) + assert i == sending + assert j == receiving + + # @pytest.mark.only_for_pool_type(1) + # class TestMeta: + # # TODO: replace with meta implementation + # @pytest.fixture + # def new_factory_setup( + # self, + # new_factory, + # amm_implementation_plain, + # pool_size, + # alice, + # fee_receiver, + # ): + # with boa.env.prank(alice): + # new_factory.set_plain_implementations(pool_size, 0, amm_implementation_plain.address) + # + # def test_factory(self, factory, swap): + # assert factory.get_meta_n_coins(swap) == [2, 4] + # + # def test_get_underlying_coins(self, factory, swap, underlying_coins, pool_size, zero_address): + # assert factory.get_underlying_coins(swap) == underlying_coins + [zero_address] * ( + # 4 - pool_size + # ) + # + # def test_get_underlying_decimals(self, factory, swap, underlying_decimals): + # assert factory.get_underlying_decimals(swap) == underlying_decimals + [0] * ( + # 4 - len(underlying_decimals) + # ) + # + # @pytest.mark.parametrize("idx", range(1, 4)) + # def test_find_pool_for_coins_underlying(self, factory, swap, underlying_coins, idx): + # assert factory.find_pool_for_coins(underlying_coins[0], underlying_coins[idx]) == swap + # assert factory.find_pool_for_coins(underlying_coins[idx], underlying_coins[0]) == swap + # + # def test_get_metapool_rates(self, factory, swap, base_pool): + # assert factory.get_metapool_rates(swap) == [10 ** 18, base_pool.get_virtual_price()] + # + # @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) + # def test_find_pool_underlying_base_pool_only(self, factory, underlying_coins, sending, receiving, zero_address): + # assert ( + # factory.find_pool_for_coins(underlying_coins[sending], underlying_coins[receiving]) + # == zero_address + # ) + # + # @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) + # def test_get_coin_indices_underlying(factory, swap, sending, receiving, underlying_coins): + # i, j, is_underlying = factory.get_coin_indices( + # swap, underlying_coins[sending], underlying_coins[receiving] + # ) + # assert i == sending + # assert j == receiving + # assert is_underlying is False + # + # @pytest.mark.parametrize("idx", range(1, 4)) + # def test_get_coin_indices_reverts(self, factory, swap, base_lp_token, underlying_coins, idx): + # with boa.reverts(): + # factory.get_coin_indices(swap, base_lp_token, underlying_coins[idx]) + # + # @pytest.mark.usefixtures("forked_chain") + # @pytest.mark.only_for_pool_type(0) + # class TestFactoryAddPools: + # def test_add_base_pool(self, factory, alice, fee_receiver, implementation_usd): + # susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" + # factory.add_base_pool( + # susd_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # assert factory.base_pool_count() == 3 + # assert factory.base_pool_list(2) == susd_pool + # assert factory.get_fee_receiver(susd_pool) == fee_receiver + # + # def test_add_base_pool_already_exists(self, factory, base_pool, alice, fee_receiver, implementation_usd): + # with boa.reverts("dev: pool exists"): + # factory.add_base_pool( + # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # + # def test_add_base_pool_only_admin(factory, base_pool, bob, fee_receiver, implementation_usd): + # with brownie.reverts("dev: admin-only function"): + # factory.add_base_pool( + # "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD", + # fee_receiver, + # 0, + # [implementation_usd] + [ZERO_ADDRESS] * 9, + # {"from": bob}, + # ) + # + # @pytest.mark.skip + # def test_deploy_metapool(MetaUSD, new_factory, new_factory_setup, base_pool, bob): + # coin = ERC20(decimals=7) + # + # tx = new_factory.deploy_metapool( + # base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob} + # ) + # assert tx.return_value == tx.new_contracts[0] + # swap = MetaUSD.at(tx.return_value) + # + # assert swap.coins(0) == coin + # assert swap.A() == 12345 + # assert swap.fee() == 50000000 + # + # assert new_factory.pool_count() == 1 + # assert new_factory.pool_list(0) == swap + # assert new_factory.get_decimals(swap) == [7, 18, 0, 0] + # + # + # @pytest.mark.skip + # def test_add_existing_metapools( + # factory, new_factory, fee_receiver, implementation_usd, base_pool, alice + # ): + # assert new_factory.pool_count() == 0 + # # add existing USD pools to new factory + # new_factory.add_base_pool( + # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # new_factory.add_existing_metapools( + # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B", "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c"] + # + [ZERO_ADDRESS] * 8 + # ) + # assert new_factory.pool_count() == 2 + # assert new_factory.pool_list(0) == "0x5a6A4D54456819380173272A5E8E9B9904BdF41B" + # assert new_factory.pool_list(1) == "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c" + # assert ( + # new_factory.get_implementation_address("0x5a6A4D54456819380173272A5E8E9B9904BdF41B") + # == "0x5F890841f657d90E081bAbdB532A05996Af79Fe6" + # ) + # + # + # @pytest.mark.skip + # def test_add_existing_metapools_unknown_pool(swap, new_factory): + # with brownie.reverts("dev: pool not in old factory"): + # new_factory.add_existing_metapools([swap] + [ZERO_ADDRESS] * 9) + # + # + # @pytest.mark.skip + # def test_add_existing_metapools_duplicate_pool( + # new_factory, base_pool, implementation_usd, fee_receiver, alice + # ): + # new_factory.add_base_pool( + # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # new_factory.add_existing_metapools( + # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 + # ) + # with brownie.reverts("dev: pool already exists"): + # new_factory.add_existing_metapools( + # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 + # ) + # + # + # def test_deploy_plain_pool( + # new_factory_setup, new_factory, decimals, bob, plain_basic, project, coins + # ): + # + # tx = new_factory.deploy_plain_pool( + # "Test Plain", + # "TST", + # coins + [ZERO_ADDRESS] * (4 - len(coins)), + # 12345, + # 50000000, + # 0, + # 0, + # {"from": bob}, + # ) + # assert tx.return_value == tx.new_contracts[0] + # swap = getattr(project, plain_basic._name).at(tx.return_value) + # + # assert swap.coins(0) == coins[0] + # assert swap.coins(1) == coins[1] + # + # assert swap.A() == 12345 + # assert swap.fee() == 50000000 + # + # assert new_factory.pool_count() == 1 + # assert new_factory.pool_list(0) == swap + # assert new_factory.get_decimals(swap) == decimals + [0] * (4 - len(decimals)) + # + # + # @pytest.mark.skip + # def test_pool_count(new_factory, coins, new_factory_setup, bob, base_pool): + # + # tx = new_factory.deploy_plain_pool( + # "Test Plain", "TST", coins, 12345, 50000000, 0, 0, {"from": bob} + # ) + # assert tx.return_value == tx.new_contracts[0] + # assert new_factory.pool_count() == 1 + # + # coin = ERC20(decimals=7) + # tx = new_factory.deploy_metapool( + # base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob} + # ) + # assert tx.return_value == tx.new_contracts[0] + # assert new_factory.pool_count() == 2 + # + # coin = ERC20(decimals=7) + # pool = Contract("0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714") + # tx = new_factory.deploy_metapool(pool, "Name", "SYM", coin, 123456, 50000000, 0, {"from": bob}) + # assert new_factory.pool_count() == 3 + # + # + # @pytest.mark.skip + # def test_deploy_plain_pool_revert(base_pool, new_factory, new_factory_setup, bob): + # coin = ERC20(decimals=7) + # new_factory.deploy_metapool(base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob}) + # existing_coin = base_pool.coins(0) + # assert new_factory.base_pool_assets(existing_coin) + # coins = [existing_coin, ERC20(decimals=9), ZERO_ADDRESS, ZERO_ADDRESS] + # # should revert because a metapool already exists for one of the coins + # with brownie.reverts("Invalid asset, deploy a metapool"): + # new_factory.deploy_plain_pool("Test Plain", "TST", coins, 12345, 50000000, {"from": bob}) From 12df2fb6f5886ea2b033159db63540f432a4087a Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 30 Jun 2023 18:09:43 +0200 Subject: [PATCH 045/337] general fixes --- .github/workflows/test_factory.yaml | 2 +- .github/workflows/test_token.yaml | 35 ++++++++++++++++++++++ README.MD | 46 +++++++++++++++++++++++++++-- tests/fixtures/pools.py | 4 +-- tests/test_factory.py | 24 ++------------- tests/{pools => }/test_token.py | 0 6 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/test_token.yaml rename tests/{pools => }/test_token.py (100%) diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index 45aaa196..b8b45070 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -1,4 +1,4 @@ -name: test_gauge +name: test_factory on: pull_request: diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml new file mode 100644 index 00000000..956497f2 --- /dev/null +++ b/.github/workflows/test_token.yaml @@ -0,0 +1,35 @@ +name: test_token + +on: [pull_request, push] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Cache Compiler Installations + uses: actions/cache@v3 + with: + path: | + ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: | + pip install poetry==1.5.1 + poetry config virtualenvs.in-project true + poetry install --no-interaction + + - name: Run Test Token + run: | + source .venv/bin/activate + pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto diff --git a/README.MD b/README.MD index 5d068cf0..21abcf85 100644 --- a/README.MD +++ b/README.MD @@ -1,7 +1,20 @@ # Stableswap NG +Permissionless deployment of Curve metapools. + +## Overview + +The metapool factory has several core components: + +* [`Factory`](contracts/main/CurveStableSwapFactoryNG.vy) is the main contract used to deploy new metapools. It also acts a registry for finding the deployed pools and querying information about them. +* New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwap2NG.vy) targetted by the proxy is determined according to the base pool. + +See the [documentation](https://curve.readthedocs.io) for more detailed information. + ## Testing +### Installation + Install dependencies using poetry (python ^3.10.4) ```shell @@ -9,8 +22,37 @@ pip install poetry==1.5.1 poetry install ``` -### Testing parameters +### Paramaters + +- `--pool-size` - size of pool (N_COINS), available parameters: `[2]` +- `--pool-type` - types of pool(divided by comma), available parameters: `[basic,meta]` +- `--token-types` - token types to test against(divided by comma), available parameters: `[plain,eth,oracle,rebasing]` +- `--decimals` - token decimals (divided by comma), default `18,18` +- `--return-types` - types of .transfer() returns to test against (divided by comma), default `revert,False,None` + + +### Type of tests + +Testing gauge + +```shell +pytest tests/gauge/ +``` + +Testing factory + +```shell +pytest tests/test_factory.py +``` + +Testing swap is ERC20 + +```shell +pytest tests/test_token.py +``` + +Testing swaps ```shell -pytest tests -n auto +pytest tests/pools/ ``` diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 80fe2c81..d4020c12 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -68,13 +68,13 @@ def swap( # <--------------------- Functions ---------------------> # TODO: add Factory Meta Implementation -@pytest.fixture +@pytest.fixture(scope="module") def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): with boa.env.prank(owner): swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture +@pytest.fixture(scope="module") def add_initial_liquidity_alice(alice, approve_alice, mint_alice, deposit_amounts, swap): with boa.env.prank(alice): swap.add_liquidity(deposit_amounts, 0) diff --git a/tests/test_factory.py b/tests/test_factory.py index 536e9e76..f2332a1e 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -5,31 +5,11 @@ MAX_COINS = 8 +# TODO: ADD meta -class TestFactory: - @pytest.fixture - def new_factory(self, alice, fee_receiver, weth): - with boa.env.prank(alice): - return boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - alice, - weth, - ) +class TestFactory: class TestBasic: - @pytest.fixture - def new_factory_setup( - self, - new_factory, - amm_implementation_plain, - pool_size, - alice, - fee_receiver, - ): - with boa.env.prank(alice): - new_factory.set_plain_implementations(pool_size, 0, amm_implementation_plain.address) - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receiving): assert ( diff --git a/tests/pools/test_token.py b/tests/test_token.py similarity index 100% rename from tests/pools/test_token.py rename to tests/test_token.py From bfab05112cf738b384b5677cf0a71994d8e76b8e Mon Sep 17 00:00:00 2001 From: Oleg Date: Sat, 1 Jul 2023 01:36:01 +0200 Subject: [PATCH 046/337] add meta pool to tests --- .github/workflows/test_factory.yaml | 9 +- .github/workflows/test_pools_2.yaml | 18 +- .github/workflows/test_token.yaml | 7 +- README.MD | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 6 +- contracts/mocks/CurvePool.vy | 891 +++++++++++++++++++++ contracts/mocks/CurveTokenV3.vy | 202 +++++ tests/conftest.py | 47 +- tests/fixtures/factory.py | 29 +- tests/fixtures/pools.py | 136 +++- tests/fixtures/tokens.py | 51 +- 11 files changed, 1330 insertions(+), 68 deletions(-) create mode 100644 contracts/mocks/CurvePool.vy create mode 100644 contracts/mocks/CurveTokenV3.vy diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index b8b45070..7e14f91f 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -38,7 +38,12 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-interaction - - name: Run Tests + - name: Run Tests Basic run: | source .venv/bin/activate - pytest tests/test_factory.py --pool-size=2 --decimals=18,18 -n auto + pytest tests/test_factory.py --pool-size=2 --pool-type=basic --decimals=18,18 -n auto + + - name: Run Tests Meta + run: | + source .venv/bin/activate + pytest tests/test_factory.py --pool-size=2 --pool-type=meta --decimals=18,18 -n auto diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml index a66e1665..92539781 100644 --- a/.github/workflows/test_pools_2.yaml +++ b/.github/workflows/test_pools_2.yaml @@ -30,12 +30,12 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-interaction - - name: Run All Token Tests 18,18 - run: | - source .venv/bin/activate - pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto - - - name: Run Plain Tests 18,6 - run: | - source .venv/bin/activate - pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto +# - name: Run All Token Tests 18,18 +# run: | +# source .venv/bin/activate +# pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto +# +# - name: Run Plain Tests 18,6 +# run: | +# source .venv/bin/activate +# pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index 956497f2..a120b2af 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -29,7 +29,12 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-interaction - - name: Run Test Token + - name: Run Test Basic run: | source .venv/bin/activate pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto + + - name: Run Test Meta + run: | + source .venv/bin/activate + pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto diff --git a/README.MD b/README.MD index 21abcf85..f7200243 100644 --- a/README.MD +++ b/README.MD @@ -25,7 +25,7 @@ poetry install ### Paramaters - `--pool-size` - size of pool (N_COINS), available parameters: `[2]` -- `--pool-type` - types of pool(divided by comma), available parameters: `[basic,meta]` +- `--pool-type` - type of pool, available parameters: `[basic,meta]` - `--token-types` - token types to test against(divided by comma), available parameters: `[plain,eth,oracle,rebasing]` - `--decimals` - token decimals (divided by comma), default `18,18` - `--return-types` - types of .transfer() returns to test against (divided by comma), default `revert,False,None` diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 8401d55f..27cc40d2 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -317,15 +317,15 @@ def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: @return uint256 list of underlying balances """ + base_pool: address = self.pool_data[_pool].base_pool + assert base_pool != empty(address) # dev: pool is not a metapool + underlying_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) underlying_balances[0] = CurvePool(_pool).balances(0) base_total_supply: uint256 = ERC20(self.pool_data[_pool].coins[1]).totalSupply() - print(base_total_supply) if base_total_supply > 0: underlying_pct: uint256 = CurvePool(_pool).balances(1) * 10**36 / base_total_supply - base_pool: address = self.pool_data[_pool].base_pool - assert base_pool != empty(address) # dev: pool is not a metapool n_coins: uint256 = self.base_pool_data[base_pool].n_coins for i in range(MAX_COINS): if i == n_coins: diff --git a/contracts/mocks/CurvePool.vy b/contracts/mocks/CurvePool.vy new file mode 100644 index 00000000..2f32ff0a --- /dev/null +++ b/contracts/mocks/CurvePool.vy @@ -0,0 +1,891 @@ +# @version 0.3.9 +""" +@title StableSwap +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Minimal pool implementation with no lending +@dev This contract is only a mock used for testing +""" + +from vyper.interfaces import ERC20 + +interface CurveToken: + def totalSupply() -> uint256: view + def mint(_to: address, _value: uint256) -> bool: nonpayable + def burnFrom(_to: address, _value: uint256) -> bool: nonpayable + + +# Events +event TokenExchange: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event AddLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event RemoveLiquidity: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + token_supply: uint256 + +event RemoveLiquidityOne: + provider: indexed(address) + token_amount: uint256 + coin_amount: uint256 + token_supply: uint256 + +event RemoveLiquidityImbalance: + provider: indexed(address) + token_amounts: uint256[N_COINS] + fees: uint256[N_COINS] + invariant: uint256 + token_supply: uint256 + +event CommitNewAdmin: + deadline: indexed(uint256) + admin: indexed(address) + +event NewAdmin: + admin: indexed(address) + +event CommitNewFee: + deadline: indexed(uint256) + fee: uint256 + admin_fee: uint256 + +event NewFee: + fee: uint256 + admin_fee: uint256 + +event RampA: + old_A: uint256 + new_A: uint256 + initial_time: uint256 + future_time: uint256 + +event StopRampA: + A: uint256 + t: uint256 + + +# These constants must be set prior to compiling +N_COINS: constant(uint256) = 3 +N_COINS_128: constant(int128) = 3 +PRECISION_MUL: constant(uint256[N_COINS]) = [1, 1, 1] +RATES: constant(uint256[N_COINS]) = [10 ** 18, 10 ** 18, 10 ** 18] + +# fixed constants +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +PRECISION: constant(uint256) = 10 ** 18 # The precision to convert to + +MAX_ADMIN_FEE: constant(uint256) = 10 * 10 ** 9 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 +MAX_A: constant(uint256) = 10 ** 6 +MAX_A_CHANGE: constant(uint256) = 10 + +ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400 +MIN_RAMP_TIME: constant(uint256) = 86400 + +coins: public(address[N_COINS]) +balances: public(uint256[N_COINS]) +fee: public(uint256) # fee * 1e10 +admin_fee: public(uint256) # admin_fee * 1e10 + +owner: public(address) +lp_token: public(address) + +A_PRECISION: constant(uint256) = 100 +initial_A: public(uint256) +future_A: public(uint256) +initial_A_time: public(uint256) +future_A_time: public(uint256) + +admin_actions_deadline: public(uint256) +transfer_ownership_deadline: public(uint256) +future_fee: public(uint256) +future_admin_fee: public(uint256) +future_owner: public(address) + +is_killed: bool +kill_deadline: uint256 +KILL_DEADLINE_DT: constant(uint256) = 2 * 30 * 86400 + + +@external +def __init__( + _owner: address, + _coins: address[N_COINS], + _pool_token: address, + _A: uint256, + _fee: uint256, + _admin_fee: uint256 +): + """ + @notice Contract constructor + @param _owner Contract owner address + @param _coins Addresses of ERC20 conracts of coins + @param _pool_token Address of the token representing LP share + @param _A Amplification coefficient multiplied by n * (n - 1) + @param _fee Fee to charge for exchanges + @param _admin_fee Admin fee + """ + for i in range(N_COINS): + assert _coins[i] != empty(address) + self.coins = _coins + self.initial_A = _A * A_PRECISION + self.future_A = _A * A_PRECISION + self.fee = _fee + self.admin_fee = _admin_fee + self.owner = _owner + self.kill_deadline = block.timestamp + KILL_DEADLINE_DT + self.lp_token = _pool_token + + +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@view +@external +def A() -> uint256: + return self._A() / A_PRECISION + + +@view +@external +def A_precise() -> uint256: + return self._A() + + +@view +@internal +def _xp() -> uint256[N_COINS]: + result: uint256[N_COINS] = RATES + for i in range(N_COINS): + result[i] = result[i] * self.balances[i] / PRECISION + return result + + +@pure +@internal +def _xp_mem(_balances: uint256[N_COINS]) -> uint256[N_COINS]: + result: uint256[N_COINS] = RATES + for i in range(N_COINS): + result[i] = result[i] * _balances[i] / PRECISION + return result + + +@pure +@internal +def _get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + Dprev: uint256 = 0 + + for _x in _xp: + S += _x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for _i in range(255): + D_P: uint256 = D + for _x in _xp: + D_P = D_P * D / (_x * N_COINS) # If division by 0, this will be borked: only withdrawal will work. And that is good + Dprev = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@view +@internal +def _get_D_mem(_balances: uint256[N_COINS], _amp: uint256) -> uint256: + return self._get_D(self._xp_mem(_balances), _amp) + + +@view +@external +def get_virtual_price() -> uint256: + """ + @notice The current virtual price of the pool LP token + @dev Useful for calculating profits + @return LP token virtual price normalized to 1e18 + """ + D: uint256 = self._get_D(self._xp(), self._A()) + # D is in the units similar to DAI (e.g. converted to precision 1e18) + # When balanced, D = n * x_u - total virtual value of the portfolio + token_supply: uint256 = ERC20(self.lp_token).totalSupply() + return D * PRECISION / token_supply + + +@view +@external +def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @dev This calculation accounts for slippage, but not fees. + Needed to prevent front-running, not for precise calculations! + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + amp: uint256 = self._A() + balances: uint256[N_COINS] = self.balances + D0: uint256 = self._get_D_mem(balances, amp) + for i in range(N_COINS): + if _is_deposit: + balances[i] += _amounts[i] + else: + balances[i] -= _amounts[i] + D1: uint256 = self._get_D_mem(balances, amp) + token_amount: uint256 = CurveToken(self.lp_token).totalSupply() + diff: uint256 = 0 + if _is_deposit: + diff = D1 - D0 + else: + diff = D0 - D1 + return diff * token_amount / D0 + + +@external +@nonreentrant('lock') +def add_liquidity(_amounts: uint256[N_COINS], _min_mint_amount: uint256) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @return Amount of LP tokens received by depositing + """ + assert not self.is_killed # dev: is killed + + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self.balances + + # Initial invariant + D0: uint256 = self._get_D_mem(old_balances, amp) + + lp_token: address = self.lp_token + token_supply: uint256 = CurveToken(lp_token).totalSupply() + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + if token_supply == 0: + assert _amounts[i] > 0 # dev: initial deposit requires all coins + # balances store amounts of c-tokens + new_balances[i] += _amounts[i] + + # Invariant after change + D1: uint256 = self._get_D_mem(new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + D2: uint256 = D1 + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + if token_supply > 0: + # Only account for fees if we are not the first to deposit + fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + admin_fee: uint256 = self.admin_fee + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = fee * difference / FEE_DENOMINATOR + self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR) + new_balances[i] -= fees[i] + D2 = self._get_D_mem(new_balances, amp) + mint_amount = token_supply * (D2 - D0) / D0 + else: + self.balances = new_balances + mint_amount = D1 # Take the dust if there was any + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Take coins from the sender + for i in range(N_COINS): + if _amounts[i] > 0: + # "safeTransferFrom" which works for ERC20s which return bool or not + _response: Bytes[32] = raw_call( + self.coins[i], + concat( + method_id("transferFrom(address,address,uint256)"), + convert(msg.sender, bytes32), + convert(self, bytes32), + convert(_amounts[i], bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) # dev: failed transfer + # end "safeTransferFrom" + + # Mint pool tokens + CurveToken(lp_token).mint(msg.sender, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, token_supply + mint_amount) + + return mint_amount + + +@view +@internal +def _get_y(i: int128, j: int128, x: uint256, _xp: uint256[N_COINS]) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + A: uint256 = self._A() + D: uint256 = self._get_D(_xp, A) + Ann: uint256 = A * N_COINS + c: uint256 = D + S: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + + for _i in range(N_COINS_128): + if _i == i: + _x = x + elif _i != j: + _x = _xp[_i] + else: + continue + S += _x + c = c * D / (_x * N_COINS) + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S + D * A_PRECISION / Ann # - D + y: uint256 = D + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@external +def get_dy(i: int128, j: int128, _dx: uint256) -> uint256: + xp: uint256[N_COINS] = self._xp() + rates: uint256[N_COINS] = RATES + + x: uint256 = xp[i] + (_dx * rates[i] / PRECISION) + y: uint256 = self._get_y(i, j, x, xp) + dy: uint256 = xp[j] - y - 1 + fee: uint256 = self.fee * dy / FEE_DENOMINATOR + return (dy - fee) * PRECISION / rates[j] + + +@external +@nonreentrant('lock') +def exchange(i: int128, j: int128, _dx: uint256, _min_dy: uint256) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert not self.is_killed # dev: is killed + + old_balances: uint256[N_COINS] = self.balances + xp: uint256[N_COINS] = self._xp_mem(old_balances) + + rates: uint256[N_COINS] = RATES + x: uint256 = xp[i] + _dx * rates[i] / PRECISION + y: uint256 = self._get_y(i, j, x, xp) + + dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" + + dy_admin_fee: uint256 = dy_fee * self.admin_fee / FEE_DENOMINATOR + dy_admin_fee = dy_admin_fee * PRECISION / rates[j] + + # Change balances exactly in same way as we change actual ERC20 coin amounts + self.balances[i] = old_balances[i] + _dx + # When rounding errors happen, we undercharge admin fee in favor of LP + self.balances[j] = old_balances[j] - dy - dy_admin_fee + + _response: Bytes[32] = raw_call( + self.coins[i], + concat( + method_id("transferFrom(address,address,uint256)"), + convert(msg.sender, bytes32), + convert(self, bytes32), + convert(_dx, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + _response = raw_call( + self.coins[j], + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(dy, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + log TokenExchange(msg.sender, i, _dx, j, dy) + + return dy + + +@external +@nonreentrant('lock') +def remove_liquidity(_amount: uint256, _min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: + """ + @notice Withdraw coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @return List of amounts of coins that were withdrawn + """ + lp_token: address = self.lp_token + total_supply: uint256 = CurveToken(lp_token).totalSupply() + amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + + for i in range(N_COINS): + old_balance: uint256 = self.balances[i] + value: uint256 = old_balance * _amount / total_supply + assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" + self.balances[i] = old_balance - value + amounts[i] = value + _response: Bytes[32] = raw_call( + self.coins[i], + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(value, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + CurveToken(lp_token).burnFrom(msg.sender, _amount) # dev: insufficient funds + + log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply - _amount) + + return amounts + + +@external +@nonreentrant('lock') +def remove_liquidity_imbalance(_amounts: uint256[N_COINS], _max_burn_amount: uint256) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @return Actual amount of the LP token burned in the withdrawal + """ + assert not self.is_killed # dev: is killed + + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self.balances + D0: uint256 = self._get_D_mem(old_balances, amp) + new_balances: uint256[N_COINS] = old_balances + for i in range(N_COINS): + new_balances[i] -= _amounts[i] + D1: uint256 = self._get_D_mem(new_balances, amp) + + fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + admin_fee: uint256 = self.admin_fee + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + new_balance: uint256 = new_balances[i] + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = fee * difference / FEE_DENOMINATOR + self.balances[i] = new_balance - (fees[i] * admin_fee / FEE_DENOMINATOR) + new_balances[i] = new_balance - fees[i] + D2: uint256 = self._get_D_mem(new_balances, amp) + + lp_token: address = self.lp_token + token_supply: uint256 = CurveToken(lp_token).totalSupply() + token_amount: uint256 = (D0 - D2) * token_supply / D0 + assert token_amount != 0 # dev: zero tokens burned + token_amount += 1 # In case of rounding errors - make it unfavorable for the "attacker" + assert token_amount <= _max_burn_amount, "Slippage screwed you" + + CurveToken(lp_token).burnFrom(msg.sender, token_amount) # dev: insufficient funds + for i in range(N_COINS): + if _amounts[i] != 0: + _response: Bytes[32] = raw_call( + self.coins[i], + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(_amounts[i], bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, token_supply - token_amount) + + return token_amount + + +@pure +@internal +def _get_y_D(A: uint256, i: int128, _xp: uint256[N_COINS], D: uint256) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + Ann: uint256 = A * N_COINS + c: uint256 = D + S: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + + for _i in range(N_COINS_128): + if _i != i: + _x = _xp[_i] + else: + continue + S += _x + c = c * D / (_x * N_COINS) + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def _calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> (uint256, uint256, uint256): + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + amp: uint256 = self._A() + xp: uint256[N_COINS] = self._xp() + D0: uint256 = self._get_D(xp, amp) + + total_supply: uint256 = CurveToken(self.lp_token).totalSupply() + D1: uint256 = D0 - _token_amount * D0 / total_supply + new_y: uint256 = self._get_y_D(amp, i, xp, D1) + xp_reduced: uint256[N_COINS] = xp + fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for j in range(N_COINS_128): + dx_expected: uint256 = 0 + if j == i: + dx_expected = xp[j] * D1 / D0 - new_y + else: + dx_expected = xp[j] - xp[j] * D1 / D0 + xp_reduced[j] -= fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self._get_y_D(amp, i, xp_reduced, D1) + precisions: uint256[N_COINS] = PRECISION_MUL + dy = (dy - 1) / precisions[i] # Withdraw less to account for rounding errors + dy_0: uint256 = (xp[i] - new_y) / precisions[i] # w/o fees + + return dy, dy_0 - dy, total_supply + + +@view +@external +def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing a single coin + @param _token_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @return Amount of coin received + """ + return self._calc_withdraw_one_coin(_token_amount, i)[0] + + +@external +@nonreentrant('lock') +def remove_liquidity_one_coin(_token_amount: uint256, i: int128, _min_amount: uint256) -> uint256: + """ + @notice Withdraw a single coin from the pool + @param _token_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_amount Minimum amount of coin to receive + @return Amount of coin received + """ + assert not self.is_killed # dev: is killed + + dy: uint256 = 0 + dy_fee: uint256 = 0 + total_supply: uint256 = 0 + dy, dy_fee, total_supply = self._calc_withdraw_one_coin(_token_amount, i) + assert dy >= _min_amount, "Not enough coins removed" + + self.balances[i] -= (dy + dy_fee * self.admin_fee / FEE_DENOMINATOR) + CurveToken(self.lp_token).burnFrom(msg.sender, _token_amount) # dev: insufficient funds + + _response: Bytes[32] = raw_call( + self.coins[i], + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(dy, bytes32), + ), + max_outsize=32, + ) + if len(_response) > 0: + assert convert(_response, bool) + + log RemoveLiquidityOne(msg.sender, _token_amount, dy, total_supply - _token_amount) + + return dy + + +### Admin functions ### +@external +def ramp_A(_future_A: uint256, _future_time: uint256): + assert msg.sender == self.owner # dev: only owner + assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME + assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time + + initial_A: uint256 = self._A() + future_A_p: uint256 = _future_A * A_PRECISION + + assert _future_A > 0 and _future_A < MAX_A + if future_A_p < initial_A: + assert future_A_p * MAX_A_CHANGE >= initial_A + else: + assert future_A_p <= initial_A * MAX_A_CHANGE + + self.initial_A = initial_A + self.future_A = future_A_p + self.initial_A_time = block.timestamp + self.future_A_time = _future_time + + log RampA(initial_A, future_A_p, block.timestamp, _future_time) + + +@external +def stop_ramp_A(): + assert msg.sender == self.owner # dev: only owner + + current_A: uint256 = self._A() + self.initial_A = current_A + self.future_A = current_A + self.initial_A_time = block.timestamp + self.future_A_time = block.timestamp + # now (block.timestamp < t1) is always False, so we return saved A + + log StopRampA(current_A, block.timestamp) + + +@external +def commit_new_fee(_new_fee: uint256, _new_admin_fee: uint256): + assert msg.sender == self.owner # dev: only owner + assert self.admin_actions_deadline == 0 # dev: active action + assert _new_fee <= MAX_FEE # dev: fee exceeds maximum + assert _new_admin_fee <= MAX_ADMIN_FEE # dev: admin fee exceeds maximum + + deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY + self.admin_actions_deadline = deadline + self.future_fee = _new_fee + self.future_admin_fee = _new_admin_fee + + log CommitNewFee(deadline, _new_fee, _new_admin_fee) + + +@external +def apply_new_fee(): + assert msg.sender == self.owner # dev: only owner + assert block.timestamp >= self.admin_actions_deadline # dev: insufficient time + assert self.admin_actions_deadline != 0 # dev: no active action + + self.admin_actions_deadline = 0 + fee: uint256 = self.future_fee + admin_fee: uint256 = self.future_admin_fee + self.fee = fee + self.admin_fee = admin_fee + + log NewFee(fee, admin_fee) + + +@external +def revert_new_parameters(): + assert msg.sender == self.owner # dev: only owner + + self.admin_actions_deadline = 0 + + +@external +def commit_transfer_ownership(_owner: address): + assert msg.sender == self.owner # dev: only owner + assert self.transfer_ownership_deadline == 0 # dev: active transfer + + deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY + self.transfer_ownership_deadline = deadline + self.future_owner = _owner + + log CommitNewAdmin(deadline, _owner) + + +@external +def apply_transfer_ownership(): + assert msg.sender == self.owner # dev: only owner + assert block.timestamp >= self.transfer_ownership_deadline # dev: insufficient time + assert self.transfer_ownership_deadline != 0 # dev: no active transfer + + self.transfer_ownership_deadline = 0 + owner: address = self.future_owner + self.owner = owner + + log NewAdmin(owner) + + +@external +def revert_transfer_ownership(): + assert msg.sender == self.owner # dev: only owner + + self.transfer_ownership_deadline = 0 + + +@view +@external +def admin_balances(i: uint256) -> uint256: + return ERC20(self.coins[i]).balanceOf(self) - self.balances[i] + + +@external +def withdraw_admin_fees(): + assert msg.sender == self.owner # dev: only owner + + for i in range(N_COINS): + coin: address = self.coins[i] + value: uint256 = ERC20(coin).balanceOf(self) - self.balances[i] + if value > 0: + _response: Bytes[32] = raw_call( + coin, + concat( + method_id("transfer(address,uint256)"), + convert(msg.sender, bytes32), + convert(value, bytes32), + ), + max_outsize=32, + ) # dev: failed transfer + if len(_response) > 0: + assert convert(_response, bool) + + +@external +def donate_admin_fees(): + assert msg.sender == self.owner # dev: only owner + for i in range(N_COINS): + self.balances[i] = ERC20(self.coins[i]).balanceOf(self) + + +@external +def kill_me(): + assert msg.sender == self.owner # dev: only owner + assert self.kill_deadline > block.timestamp # dev: deadline has passed + self.is_killed = True + + +@external +def unkill_me(): + assert msg.sender == self.owner # dev: only owner + self.is_killed = False diff --git a/contracts/mocks/CurveTokenV3.vy b/contracts/mocks/CurveTokenV3.vy new file mode 100644 index 00000000..3143077b --- /dev/null +++ b/contracts/mocks/CurveTokenV3.vy @@ -0,0 +1,202 @@ +# @version 0.3.9 +""" +@title Curve LP Token +@author Curve.Fi +@notice Base implementation for an LP token provided for + supplying liquidity to `StableSwap` +@dev Follows the ERC-20 token standard as defined at + https://eips.ethereum.org/EIPS/eip-20 +@dev This contract is only a mock used for testing +""" + +from vyper.interfaces import ERC20 + +implements: ERC20 + +interface Curve: + def owner() -> address: view + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) + +minter: public(address) + + +@external +def __init__(_name: String[64], _symbol: String[32]): + self.name = _name + self.symbol = _symbol + self.minter = msg.sender + log Transfer(ZERO_ADDRESS, msg.sender, 0) + + +@view +@external +def decimals() -> uint256: + """ + @notice Get the number of decimals for this token + @dev Implemented as a view method to reduce gas costs + @return uint256 decimal places + """ + return 18 + + +@external +def transfer(_to : address, _value : uint256) -> bool: + """ + @dev Transfer token for a specified address + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + # NOTE: vyper does not allow underflows + # so the following subtraction would revert on insufficient balance + self.balanceOf[msg.sender] -= _value + self.balanceOf[_to] += _value + + log Transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from : address, _to : address, _value : uint256) -> bool: + """ + @dev Transfer tokens from one address to another. + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != MAX_UINT256: + self.allowance[_from][msg.sender] = _allowance - _value + + log Transfer(_from, _to, _value) + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk + that someone may use both the old and new allowance by unfortunate + transaction ordering. This may be mitigated with the use of + {increaseAllowance} and {decreaseAllowance}. + https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + + log Approval(msg.sender, _spender, _value) + return True + + +@external +def increaseAllowance(_spender: address, _added_value: uint256) -> bool: + """ + @notice Increase the allowance granted to `_spender` by the caller + @dev This is alternative to {approve} that can be used as a mitigation for + the potential race condition + @param _spender The address which will transfer the funds + @param _added_value The amount of to increase the allowance + @return bool success + """ + allowance: uint256 = self.allowance[msg.sender][_spender] + _added_value + self.allowance[msg.sender][_spender] = allowance + + log Approval(msg.sender, _spender, allowance) + return True + + +@external +def decreaseAllowance(_spender: address, _subtracted_value: uint256) -> bool: + """ + @notice Decrease the allowance granted to `_spender` by the caller + @dev This is alternative to {approve} that can be used as a mitigation for + the potential race condition + @param _spender The address which will transfer the funds + @param _subtracted_value The amount of to decrease the allowance + @return bool success + """ + allowance: uint256 = self.allowance[msg.sender][_spender] - _subtracted_value + self.allowance[msg.sender][_spender] = allowance + + log Approval(msg.sender, _spender, allowance) + return True + + +@external +def mint(_to: address, _value: uint256) -> bool: + """ + @dev Mint an amount of the token and assigns it to an account. + This encapsulates the modification of balances such that the + proper events are emitted. + @param _to The account that will receive the created tokens. + @param _value The amount that will be created. + """ + assert msg.sender == self.minter + + self.totalSupply += _value + self.balanceOf[_to] += _value + + log Transfer(ZERO_ADDRESS, _to, _value) + return True + + +@external +def burnFrom(_to: address, _value: uint256) -> bool: + """ + @dev Burn an amount of the token from a given account. + @param _to The account whose tokens will be burned. + @param _value The amount that will be burned. + """ + assert msg.sender == self.minter + + self.totalSupply -= _value + self.balanceOf[_to] -= _value + + log Transfer(_to, ZERO_ADDRESS, _value) + return True + + +@external +def set_minter(_minter: address): + assert msg.sender == self.minter + self.minter = _minter + + +@external +def set_name(_name: String[64], _symbol: String[32]): + assert Curve(self.minter).owner() == msg.sender + self.name = _name + self.symbol = _symbol + + +@external +def _mint_for_testing(_target: address, _value: uint256) -> bool: + self.totalSupply += _value + self.balanceOf[_target] += _value + log Transfer(empty(address), _target, _value) + + return True diff --git a/tests/conftest.py b/tests/conftest.py index 7ecd3a25..2176b0da 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,12 +24,11 @@ def pytest_addoption(parser): default="2", help="pool size to test against", ) - # TODO: add meta implementation parser.addoption( "--pool-type", action="store", default="basic", - help="comma-separated list of pool types to test against", + help="pool type to test against", ) parser.addoption( "--token-types", @@ -65,32 +64,42 @@ def pytest_generate_tests(metafunc): ids=[f"(PoolSize={pool_size})"], ) + pool_type = metafunc.config.getoption("pool_type") + if "pool_type" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("pool_type").split(",") - pool_type_ids = [pool_types[v] for v in cli_options] metafunc.parametrize( "pool_type", - pool_type_ids, + [pool_types[pool_type]], indirect=True, - ids=[f"(PoolType={i})" for i in cli_options], + ids=[f"(PoolType={pool_type})"], ) if "pool_token_types" in metafunc.fixturenames: cli_options = metafunc.config.getoption("token_types").split(",") - combs = list(combinations(cli_options, pool_size)) - if pool_size == 2: - # do not include (eth,eth) pair - for t in cli_options: - if t != "eth": - combs.append((t, t)) - - metafunc.parametrize( - "pool_token_types", - [(token_types[c[0]], token_types[c[1]]) for c in combs], - indirect=True, - ids=[f"(PoolTokenTypes={c})" for c in combs], - ) + if pool_types[pool_type] == 0: + combs = list(combinations(cli_options, pool_size)) + if pool_size == 2: + # do not include (eth,eth) pair + for t in cli_options: + if t != "eth": + combs.append((t, t)) + + metafunc.parametrize( + "pool_token_types", + [(token_types[c[0]], token_types[c[1]]) for c in combs], + indirect=True, + ids=[f"(PoolTokenTypes={c})" for c in combs], + ) + else: + # workaround for generating tokens + # for meta pool only 1st coin is selected + metafunc.parametrize( + "pool_token_types", + [[token_types[c]] for c in cli_options], + indirect=True, + ids=[f"(PoolTokenTypes={c})" for c in cli_options], + ) if "initial_decimals" in metafunc.fixturenames: cli_options = metafunc.config.getoption("decimals") diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 1b2a685b..aaa8e725 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -56,13 +56,40 @@ def factory( # <--------------------- Functions ---------------------> -# TODO: add Factory Meta Implementation @pytest.fixture(scope="module") def set_plain_implementations(owner, factory, pool_size, pool_type, amm_implementation_plain): with boa.env.prank(owner): factory.set_plain_implementations(pool_size, pool_type, amm_implementation_plain.address) +# <--------------------- Metapool configuration ---------------------> +@pytest.fixture(scope="module") +def set_meta_implementations( + owner, + fee_receiver, + factory, + pool_size, + pool_type, + amm_implementation_meta, + base_pool, + base_pool_lp_token, + base_pool_tokens, + zero_address, +): + with boa.env.prank(owner): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + fee_receiver, + [t.address for t in base_pool_tokens], + 0, + len(base_pool_tokens), + [False] * len(base_pool_tokens), + [zero_address] * len((base_pool_tokens)), + ) + factory.set_metapool_implementations(base_pool.address, [amm_implementation_meta.address] + [zero_address] * 9) + + @pytest.fixture(scope="module") def set_gauge_implementation(owner, factory, gauge_implementation): with boa.env.prank(owner): diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index d4020c12..f050700c 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,72 +2,152 @@ import pytest from eth_utils import function_signature_to_4byte_selector -# TODO: meta pool - +# Only initialize useful fixtures @pytest.fixture(scope="module") def swap( - owner, - mint_owner, + request, + deployer, factory, weth, + pool_type, pool_token_types, pool_tokens, - amm_interface_plain, - amm_implementation_plain, - set_plain_implementations, zero_address, ): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") + if pool_type == 0: + amm_interface_plain = request.getfixturevalue("amm_interface_plain") + _ = request.getfixturevalue("set_plain_implementations") + + A = 2000 + fee = 1000000 + method_ids = [bytes(b"")] * 8 + oracles = [zero_address] * 8 + asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + is_rebasing = [False] * 8 + + for i, t in enumerate(pool_token_types): + if t == 0: + A = 2000 + fee = 1000000 + asset_type = 0 + elif t == 1: + A = 1000 + fee = 3000000 + asset_type = 1 + elif t == 2: + A = 1000 + fee = 3000000 + asset_type = 1 + method_ids[i] = oracle_method_id + oracles[i] = pool_tokens[i].address + elif t == 3: + A = 500 + fee = 4000000 + asset_type = 1 + is_rebasing[i] = True + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [pool_tokens[0].address, pool_tokens[1].address, *[zero_address] * 6], + A, + fee, + 866, + method_ids, + oracles, + asset_type, + 0, + is_rebasing, + ) + return amm_interface_plain.at(pool) - A = 2000 - fee = 1000000 - method_ids = [bytes(b"")] * 8 - oracles = [zero_address] * 8 - asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other - is_rebasing = [False] * 8 + elif pool_type == 1: + base_pool = request.getfixturevalue("base_pool") + underlying_tokens = request.getfixturevalue("underlying_tokens") + amm_interface_meta = request.getfixturevalue("amm_interface_meta") + _ = request.getfixturevalue("set_meta_implementations") - for i, t in enumerate(pool_token_types): - if t == 0: + A = 2000 + fee = 1000000 + method_id = bytes(b"") + oracle = zero_address + asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + is_rebasing = False + metapool_token_type = pool_token_types[0] + + if metapool_token_type == 0: A = 2000 fee = 1000000 asset_type = 0 - elif t == 1: + elif metapool_token_type == 1: A = 1000 fee = 3000000 asset_type = 1 - elif t == 2: + elif metapool_token_type == 2: A = 1000 fee = 3000000 asset_type = 1 - method_ids[i] = oracle_method_id - oracles[i] = pool_tokens[i].address - elif t == 3: + method_id = oracle_method_id + oracle = underlying_tokens[0].address + + elif metapool_token_type == 3: A = 500 fee = 4000000 asset_type = 1 - is_rebasing[i] = True + is_rebasing = True - with boa.env.prank(owner): - pool = factory.deploy_plain_pool( + pool = factory.deploy_metapool( + base_pool.address, "test", "test", - [pool_tokens[0].address, pool_tokens[1].address, *[zero_address] * 6], + pool_tokens[0].address, A, fee, 866, - method_ids, - oracles, + method_id, + oracle, asset_type, 0, is_rebasing, ) - return amm_interface_plain.at(pool) + return amm_interface_meta.at(pool) + + else: + raise ValueError("Wrong pool type") + + +# <--------------------- Metapool configuration ---------------------> +@pytest.fixture(scope="module") +def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): + with boa.env.prank(deployer): + base_pool = boa.load( + "contracts/mocks/CurvePool.vy", + owner, + [t.address for t in base_pool_tokens], + base_pool_lp_token.address, + 200, + 3000000, + 5000000000, + ) + base_pool_lp_token.set_minter(base_pool.address) + + amount = 1_000_000 + with boa.env.prank(alice): + for d, token in zip(base_pool_decimals, base_pool_tokens): + token._mint_for_testing(alice, amount * 10**d) + token.approve(base_pool.address, 2**256 - 1) + + base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) + base_pool_lp_token.transfer(zero_address, base_pool_lp_token.balanceOf(alice)) + + return base_pool # <--------------------- Functions ---------------------> -# TODO: add Factory Meta Implementation @pytest.fixture(scope="module") def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): with boa.env.prank(owner): diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index badbc6e3..00d8a293 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -6,8 +6,8 @@ def plain_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", decimals[0])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", decimals[1])) + for i, d in enumerate(decimals): + tokens.append(boa.load("contracts/mocks/ERC20.vy", f"TKN{i}", f"TKN{i}", decimals[i])) return tokens @@ -46,8 +46,8 @@ def oracle_tokens(deployer, decimals): def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "downETH", "downETH", decimals[0], False)) - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", "stETH", "stETH", decimals[1], True)) + for i, d in enumerate(decimals): + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], i != 0)) return tokens @@ -70,6 +70,49 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke return pool_tokens +# <--------------------- Metapool configuration ---------------------> +@pytest.fixture(scope="module") +def base_pool_decimals(): + return [18, 6, 6] + + +@pytest.fixture(scope="module") +def base_pool_tokens(deployer, base_pool_decimals): + tokens = [] + with boa.env.prank(deployer): + tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", base_pool_decimals[2])) + return tokens + + +@pytest.fixture(scope="module") +def base_pool_lp_token(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/mocks/CurveTokenV3.vy", "LP", "LP") + + +@pytest.fixture(scope="module") +def underlying_tokens( + pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens, base_pool_tokens, base_pool_lp_token +): + pool_tokens = [] + metapool_token_type = pool_token_types[0] + if metapool_token_type == 0: + pool_tokens.append(plain_tokens[0]) + elif metapool_token_type == 1: + pool_tokens = pool_tokens.append(weth) + elif metapool_token_type == 2: + pool_tokens.append(oracle_tokens[0]) + elif metapool_token_type == 3: + pool_tokens.append(rebase_tokens[0]) + else: + raise ValueError("Wrong pool token type") + + return pool_tokens + [base_pool_lp_token.address, *[t.address for t in base_pool_tokens]] + + +# <--------------------- Gauge rewards ---------------------> @pytest.fixture(scope="module") def coin_reward(owner): with boa.env.prank(owner): From 3440f5c8642a4b0b2e36e9842efda5c8c2c2d926 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:34:53 +0200 Subject: [PATCH 047/337] fix: admin fee withdrawal in metapools; fix: metapool implementations are independent of base pool --- contracts/main/CurveStableSwap2NG.vy | 1 - contracts/main/CurveStableSwapFactoryNG.vy | 66 ++++++++-------------- contracts/main/CurveStableSwapMetaNG.vy | 27 ++++++--- 3 files changed, 40 insertions(+), 54 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 048b758e..ef0998a2 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -38,7 +38,6 @@ implements: ERC20 # ------------------------------- Interfaces --------------------------------- interface Factory: - def convert_fees() -> bool: nonpayable def get_fee_receiver(_pool: address) -> address: view def admin() -> address: view diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 93d0125c..ea6aa788 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -16,7 +16,6 @@ struct PoolArray: asset_type: uint256 struct BasePoolArray: - implementations: address[10] lp_token: address fee_receiver: address coins: address[MAX_COINS] @@ -103,10 +102,9 @@ base_pool_data: public(HashMap[address, BasePoolArray]) # asset -> is used in a metapool? base_pool_assets: public(HashMap[address, bool]) -# number of coins -> implementation addresses -# for "plain pools" (as opposed to metapools), implementation contracts -# are organized according to the number of coins in the pool -plain_implementations: public(HashMap[uint256, address[10]]) +# index -> implementation address +plain_implementations: public(HashMap[uint256, address]) +metapool_implementations: public(HashMap[uint256, address]) # fee receiver for plain pools fee_receiver: address @@ -131,17 +129,6 @@ def __init__(_fee_receiver: address, _owner: address, _weth: address): # <--- Factory Getters ---> -@view -@external -def metapool_implementations(_base_pool: address) -> address[10]: - """ - @notice Get a list of implementation contracts for metapools targetting the given base pool - @dev A base pool is the pool for the LP token contained within the metapool - @param _base_pool Address of the base pool - @return List of implementation contract addresses - """ - return self.base_pool_data[_base_pool].implementations - @view @external @@ -557,7 +544,7 @@ def deploy_plain_pool( break assert coin != _coins[x+1], "Duplicate coins" - implementation: address = self.plain_implementations[n_coins][_implementation_idx] + implementation: address = self.plain_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" pool: address = create_from_blueprint( implementation, @@ -650,7 +637,7 @@ def deploy_metapool( assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" - implementation: address = self.base_pool_data[_base_pool].implementations[_implementation_idx] + implementation: address = self.metapool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" # things break if a token has >18 decimals @@ -750,7 +737,6 @@ def add_base_pool( _asset_type: uint256, _n_coins: uint256, _is_rebasing: bool[MAX_COINS], - _implementations: address[10], ): """ @notice Add a base pool to the registry, which may be used in factory metapools @@ -759,7 +745,6 @@ def add_base_pool( @param _fee_receiver Admin fee receiver address for metapools using this base pool @param _asset_type Asset type for pool, as an integer 0 = USD, 1 = ETH, 2 = BTC, 3 = Other @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing - @param _implementations List of implementation addresses that can be used with this base pool """ assert msg.sender == self.admin # dev: admin-only function assert self.base_pool_data[_base_pool].coins[0] == empty(address) # dev: pool exists @@ -775,12 +760,6 @@ def add_base_pool( if _asset_type != 0: self.base_pool_data[_base_pool].asset_type = _asset_type - for i in range(10): - implementation: address = _implementations[i] - if implementation == empty(address): - break - self.base_pool_data[_base_pool].implementations[i] = implementation - decimals: uint256 = 0 coins: address[MAX_COINS] = _coins for i in range(MAX_COINS): @@ -798,29 +777,18 @@ def add_base_pool( @external def set_metapool_implementations( - _base_pool: address, - _implementations: address[10], + _implementation_index: uint256, + _implementation: address, ): """ @notice Set implementation contracts for a metapool @dev Only callable by admin - @param _base_pool Pool address to add - @param _implementations Implementation address to use when deploying metapools + @param _implementation_index Implementation index where implementation is stored + @param _implementation Implementation address to use when deploying metapools """ - - # TODO: ensure only one implementation can be set at a time - assert msg.sender == self.admin # dev: admin-only function - assert self.base_pool_data[_base_pool].coins[0] != empty(address) # dev: base pool does not exist + self.metapool_implementations[_implementation_index] = _implementation - for i in range(10): - new_imp: address = _implementations[i] - current_imp: address = self.base_pool_data[_base_pool].implementations[i] - if new_imp == current_imp: - if new_imp == empty(address): - break - else: - self.base_pool_data[_base_pool].implementations[i] = new_imp @external @@ -829,14 +797,24 @@ def set_plain_implementations( _implementation_index: uint256, _implementation: address, ): + """ + @notice Set implementation contracts for plain pools + @dev Only callable by admin + @param _implementation_index Implementation index where implementation is stored + @param _implementation Implementation address to use when deploying plain pools + """ assert msg.sender == self.admin # dev: admin-only function - self.plain_implementations[_n_coins][_implementation_index] = _implementation + self.plain_implementations[_implementation_index] = _implementation @external def set_gauge_implementation(_gauge_implementation: address): + """ + @notice Set implementation contracts for liquidity gauge + @dev Only callable by admin + @param _implementation Implementation address to use when deploying gauges + """ assert msg.sender == self.admin # dev: admin-only function - self.gauge_implementation = _gauge_implementation diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 68cc664d..9f01ab58 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -27,7 +27,7 @@ implements: ERC20 # ------------------------------- Interfaces --------------------------------- interface Factory: - def convert_fees() -> bool: nonpayable + def convert_metapool_fees() -> bool: nonpayable def get_fee_receiver(_pool: address) -> address: view def admin() -> address: view @@ -1145,18 +1145,27 @@ def _exchange_underlying( @internal def _withdraw_admin_fees(): - receiver: address = Factory(self.factory).get_fee_receiver(self) + factory: Factory = Factory(self.factory) amounts: uint256[N_COINS] = self.admin_balances + coins: address[N_COINS] = self.coins - for i in range(N_COINS): + if amounts[0] > 0: - if amounts[i] > 0: + assert ERC20(coins[0]).transfer( + factory.address, + amounts[0], + default_return_value=True + ) + factory.convert_metapool_fees() - assert ERC20(self.coins[i]).transfer( - receiver, - amounts[i], - default_return_value=True - ) + if amounts[1] > 0: + + receiver: address = factory.get_fee_receiver(self) + assert ERC20(coins[1]).transfer( + receiver, + amounts[1], + default_return_value=True + ) self.admin_balances = empty(uint256[N_COINS]) From 9535a99b6498263e64ce6fd71e5d8c6d48200da8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:25:40 +0200 Subject: [PATCH 048/337] wip: views method; reduce codesize --- contracts/main/CurveStableSwap2NG.vy | 114 ++-- contracts/main/CurveStableSwapFactoryNG.vy | 16 +- contracts/main/CurveStableSwapMetaNG.vy | 299 ++++------ contracts/main/CurveStableSwapNGViews.vy | 515 ++++++++++++++++++ tests/fixtures/factory.py | 12 +- .../factory/test_meta_implementations.py | 3 + 6 files changed, 682 insertions(+), 277 deletions(-) create mode 100644 contracts/main/CurveStableSwapNGViews.vy create mode 100644 tests/unitary/factory/test_meta_implementations.py diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index ef0998a2..6fb57e26 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -40,6 +40,7 @@ implements: ERC20 interface Factory: def get_fee_receiver(_pool: address) -> address: view def admin() -> address: view + def views_implementation() -> address: view interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view @@ -48,6 +49,15 @@ interface WETH: def deposit(): payable def withdraw(_amount: uint256): nonpayable +interface StableSwapViews: + def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def calc_token_amount( + _amounts: uint256[MAX_COINS], + _is_deposit: bool, + _pool: address + ) -> uint256: view + # --------------------------------- Events ----------------------------------- event Transfer: @@ -118,8 +128,11 @@ N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 IS_REBASING: immutable(bool[N_COINS]) -factory: public(address) -coins: public(address[N_COINS]) +# to denote that it is a plain pool: +BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 + +factory: public(immutable(Factory)) +coins: public(immutable(address[N_COINS])) stored_balances: uint256[N_COINS] fee: public(uint256) # fee * 1e10 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -225,19 +238,18 @@ def __init__( name = _name symbol = _symbol + factory = Factory(msg.sender) + coins = [_coins[0], _coins[1]] for i in range(N_COINS): - coin: address = _coins[i] - - if coin == empty(address): + if _coins[i] == empty(address): break # Enforce native token as coin[0] (makes integrators happy) - if coin == WETH20 and i > 0: + if _coins[i] == WETH20 and i > 0: raise "ETH must be at index 0" - self.coins[i] = coin self.rate_multipliers[i] = _rate_multipliers[i] self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) @@ -245,7 +257,6 @@ def __init__( self.initial_A = A self.future_A = A self.fee = _fee - self.factory = msg.sender assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time @@ -667,7 +678,6 @@ def remove_liquidity_imbalance( rates: uint256[N_COINS] = self._stored_rates() old_balances: uint256[N_COINS] = self._balances() D0: uint256 = self.get_D_mem(rates, old_balances, amp) - coins: address[N_COINS] = self.coins new_balances: uint256[N_COINS] = old_balances for i in range(N_COINS): @@ -732,7 +742,6 @@ def remove_liquidity( total_supply: uint256 = self.totalSupply amounts: uint256[N_COINS] = empty(uint256[N_COINS]) balances: uint256[N_COINS] = self._balances() - coins: address[N_COINS] = self.coins for i in range(N_COINS): value: uint256 = balances[i] * _burn_amount / total_supply @@ -789,7 +798,6 @@ def _exchange( rates: uint256[N_COINS] = self._stored_rates() old_balances: uint256[N_COINS] = self._balances() xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - coins: address[N_COINS] = self.coins # --------------------------- Do Transfer in ----------------------------- @@ -866,7 +874,6 @@ def _add_liquidity( amp: uint256 = self._A() old_balances: uint256[N_COINS] = self._balances() rates: uint256[N_COINS] = self._stored_rates() - coins: address[N_COINS] = self.coins # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) @@ -989,7 +996,7 @@ def _add_liquidity( @internal def _withdraw_admin_fees(): - receiver: address = Factory(self.factory).get_fee_receiver(self) + receiver: address = factory.get_fee_receiver(self) amounts: uint256[N_COINS] = self.admin_balances for i in range(N_COINS): @@ -1546,12 +1553,7 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - - y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee) - x: uint256 = self.get_y(j, i, y, xp, 0, 0) - return (x - xp[i]) * PRECISION / rates[i] + return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) @view @@ -1565,14 +1567,7 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp, 0, 0) - dy: uint256 = xp[j] - y - 1 - fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - fee) * PRECISION / rates[j] + return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) @view @@ -1613,51 +1608,14 @@ def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if _is_deposit: - new_balances[i] += amount - else: - new_balances[i] -= amount - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - if total_supply > 0: - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - new_balances[i] -= base_fee * difference / FEE_DENOMINATOR - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2 = self.get_D(xp, amp) - else: - return D1 # Take the dust if there was any + amounts: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + for i in range(MAX_COINS): + if i == N_COINS: + break + amounts[i] = _amounts[i] - diff: uint256 = 0 - if _is_deposit: - diff = D2 - D0 - else: - diff = D0 - D2 - return diff * total_supply / D0 + views: address = factory.views_implementation() + return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) @view @@ -1704,12 +1662,18 @@ def oracle(_idx: uint256) -> address: return empty(address) +@view +@external +def stored_rates(i: uint256) -> uint256: + return self._stored_rates()[i] + + # --------------------------- AMM Admin Functions ---------------------------- @external def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time @@ -1732,7 +1696,7 @@ def ramp_A(_future_A: uint256, _future_time: uint256): @external def stop_ramp_A(): - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner current_A: uint256 = self._A() self.initial_A = current_A @@ -1747,7 +1711,7 @@ def stop_ramp_A(): @external def apply_new_fee(_new_fee: uint256): - assert msg.sender == Factory(self.factory).admin() + assert msg.sender == factory.admin() assert _new_fee <= MAX_FEE self.fee = _new_fee @@ -1760,7 +1724,7 @@ def set_ma_exp_time(_ma_exp_time: uint256): @notice Set the moving average window of the price oracle. @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index ea6aa788..40ecc5cf 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -105,12 +105,12 @@ base_pool_assets: public(HashMap[address, bool]) # index -> implementation address plain_implementations: public(HashMap[uint256, address]) metapool_implementations: public(HashMap[uint256, address]) +gauge_implementation: public(address) +views_implementation: public(address) # fee receiver for plain pools fee_receiver: address -gauge_implementation: public(address) - # mapping of coins -> pools for trading # a mapping key is generated for each pair of addresses via # `bitwise_xor(convert(a, uint256), convert(b, uint256))` @@ -793,7 +793,6 @@ def set_metapool_implementations( @external def set_plain_implementations( - _n_coins: uint256, _implementation_index: uint256, _implementation: address, ): @@ -818,6 +817,17 @@ def set_gauge_implementation(_gauge_implementation: address): self.gauge_implementation = _gauge_implementation +@external +def set_views_implementation(_views_implementation: address): + """ + @notice Set implementation contracts for Views methods + @dev Only callable by admin + @param _implementation Implementation address of views contract + """ + assert msg.sender == self.admin # dev: admin-only function + self.views_implementation = _views_implementation + + @external def batch_set_pool_asset_type(_pools: address[32], _asset_types: uint256[32]): """ diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9f01ab58..b22c228b 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -30,6 +30,7 @@ interface Factory: def convert_metapool_fees() -> bool: nonpayable def get_fee_receiver(_pool: address) -> address: view def admin() -> address: view + def views_implementation() -> address: view interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view @@ -55,6 +56,21 @@ interface Curve4: def calc_token_amount(amounts: uint256[4], deposit: bool) -> uint256: view def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable +interface StableSwapViews: + def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy_underlying( + i: int128, j: int128, dy: uint256, pool: address + ) -> uint256: view + def get_dx_underlying( + i: int128, j: int128, dy: uint256, pool: address + ) -> uint256: view + def calc_token_amount( + _amounts: uint256[MAX_COINS], + _is_deposit: bool, + _pool: address + ) -> uint256: view + # --------------------------------- Events ----------------------------------- event Transfer: @@ -126,7 +142,7 @@ event ApplyNewFee: MAX_COINS: constant(uint256) = 8 -N_COINS: constant(uint256) = 2 +N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 MAX_COIN: constant(int128) = N_COINS - 1 PRECISION: constant(uint256) = 10 ** 18 @@ -134,12 +150,12 @@ PRECISION: constant(uint256) = 10 ** 18 # token: is_rebasing flag is_rebasing: HashMap[address, bool] -BASE_POOL: immutable(address) -BASE_N_COINS: immutable(uint256) -BASE_COINS: immutable(address[MAX_COINS]) +BASE_POOL: public(immutable(address)) +BASE_N_COINS: public(immutable(uint256)) +BASE_COINS: public(immutable(address[MAX_COINS])) -factory: public(address) -coins: public(address[N_COINS]) +factory: public(immutable(Factory)) +coins: public(immutable(address[N_COINS])) stored_balances: uint256[N_COINS] fee: public(uint256) # fee * 1e10 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -245,14 +261,14 @@ def __init__( """ name = _name symbol = _symbol - - self.coins = [_coin, _base_lp_token] - self.rate_multipliers = _rate_multiplier - self.oracles = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) + coins = [_coin, _base_lp_token] + factory = Factory(msg.sender) BASE_COINS = _base_coins BASE_POOL = _base_pool + self.rate_multipliers = _rate_multiplier + self.oracles = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) self.is_rebasing[_coin] = _is_rebasing[0] self.is_rebasing[_base_lp_token] = False @@ -274,7 +290,6 @@ def __init__( self.initial_A = A self.future_A = A self.fee = _fee - self.factory = msg.sender assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time @@ -681,7 +696,6 @@ def add_liquidity( amp: uint256 = self._A() old_balances: uint256[N_COINS] = self._balances() rates: uint256[N_COINS] = self._stored_rates() - coins: address[N_COINS] = self.coins # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) @@ -819,7 +833,6 @@ def remove_liquidity_imbalance( rates: uint256[N_COINS] = self._stored_rates() old_balances: uint256[N_COINS] = self._balances() D0: uint256 = self.get_D_mem(rates, old_balances, amp) - coins: address[N_COINS] = self.coins new_balances: uint256[N_COINS] = old_balances for i in range(N_COINS): @@ -886,7 +899,6 @@ def remove_liquidity( total_supply: uint256 = self.totalSupply amounts: uint256[N_COINS] = empty(uint256[N_COINS]) balances: uint256[N_COINS] = self._balances() - coins: address[N_COINS] = self.coins for i in range(N_COINS): value: uint256 = balances[i] * _burn_amount / total_supply @@ -922,6 +934,36 @@ def withdraw_admin_fees(): # ------------------------ AMM Internal Functions ---------------------------- +@internal +def _exchange_core( + dx: uint256, + x: uint256, + xp: uint256[N_COINS], + rates: uint256[N_COINS], + i: int128, + j: int128, +) -> uint256: + + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(i, j, x, xp, amp, D) + + dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + + self.admin_balances[j] += ( + dy_fee * ADMIN_FEE / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # D is not changed because we did not apply a fee + self.save_p([x, y], amp, D) + + return dy + + @internal def _exchange( sender: address, @@ -941,7 +983,6 @@ def _exchange( rates: uint256[N_COINS] = self._stored_rates() old_balances: uint256[N_COINS] = self._balances() xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - coins: address[N_COINS] = self.coins # --------------------------- Do Transfer in ----------------------------- @@ -974,28 +1015,9 @@ def _exchange( # ------------------------------------------------------------------------ x: uint256 = xp[i] + dx * rates[i] / PRECISION - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(i, j, x, xp, amp, D) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] + dy: uint256 = self._exchange_core(dx, x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" - self.admin_balances[j] += ( - dy_fee * ADMIN_FEE / FEE_DENOMINATOR - ) * PRECISION / rates[j] - - # xp is not used anymore, so we reuse it for price calc - xp[i] = x - xp[j] = y - # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) - # --------------------------- Do Transfer out ---------------------------- assert ERC20(coins[j]).transfer(receiver, dy, default_return_value=True) @@ -1093,23 +1115,7 @@ def _exchange_underlying( x = dx_w_fee * rates[MAX_COIN] / PRECISION x += xp[MAX_COIN] - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) - - # Either a real coin or token - dy = xp[meta_j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - # Works for both pool coins and real coins - dy = (dy - dy_fee) * PRECISION / rates[meta_j] - - dy_admin_fee: uint256 = dy_fee * ADMIN_FEE / FEE_DENOMINATOR - dy_admin_fee = dy_admin_fee * PRECISION / rates[meta_j] - - self.admin_balances[meta_j] += dy_admin_fee - self.stored_balances[meta_j] -= dy + dy = self._exchange_core(dx_w_fee, x, xp, rates, meta_i, meta_j) # Withdraw from the base pool if needed if j > 0: @@ -1119,11 +1125,8 @@ def _exchange_underlying( assert dy >= _min_dy - # xp is not used anymore, so we reuse it for price calc - xp[meta_i] = x - xp[meta_j] = y - # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) + # Adjust stored balances: + self.stored_balances[meta_j] -= dy else: # base pool swap (user should swap at base pool for better gas) @@ -1145,9 +1148,7 @@ def _exchange_underlying( @internal def _withdraw_admin_fees(): - factory: Factory = Factory(self.factory) amounts: uint256[N_COINS] = self.admin_balances - coins: address[N_COINS] = self.coins if amounts[0] > 0: @@ -1230,6 +1231,25 @@ def _meta_calc_token_amounts_deposit( # --------------------------- AMM Math Functions ----------------------------- +@internal +@pure +def newton_y(b: uint256, c: uint256, D: uint256, _y: uint256) -> uint256: + + y_prev: uint256 = 0 + y: uint256 = _y + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + @view @internal @@ -1267,7 +1287,6 @@ def get_y( D = self.get_D(xp, amp) S_: uint256 = 0 _x: uint256 = 0 - y_prev: uint256 = 0 c: uint256 = D Ann: uint256 = amp * N_COINS @@ -1285,17 +1304,7 @@ def get_y( b: uint256 = S_ + D * A_PRECISION / Ann # - D y: uint256 = D - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise + return self.newton_y(b, c, D, y) @pure @@ -1369,17 +1378,7 @@ def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: b: uint256 = S_ + D * A_PRECISION / Ann y: uint256 = D - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise + return self.newton_y(b, c, D, y) @view @@ -1769,19 +1768,14 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - - y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - self.fee) - x: uint256 = self.get_y(j, i, y, xp, 0, 0) - return (x - xp[i]) * PRECISION / rates[i] + return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) @view @external -def get_dx_underlying(i: int128, j: int128, dx: uint256) -> uint256: +def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: # TODO: Needs get_dx_underlying - return 0 + return StableSwapViews(factory.views_implementation()).get_dx_underlying(i, j, dy, self) @view @@ -1795,15 +1789,7 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - - x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp, 0, 0) - dy: uint256 = xp[j] - y - 1 - fee: uint256 = self.fee * dy / FEE_DENOMINATOR - return (dy - fee) * PRECISION / rates[j] - + return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) @view @external @@ -1816,55 +1802,7 @@ def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ - - # TODO: Ivan needs to check this implementation - - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - - x: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - - if i != 0: - base_i = i - MAX_COIN - meta_i = 1 - if j != 0: - base_j = j - MAX_COIN - meta_j = 1 - - if i == 0: - x = xp[i] + dx * (rates[0] / 10**18) - else: - if j == 0: - # i is from BasePool - x = self._meta_calc_token_amounts_deposit(dx, base_i, rates[1]) - # Accounting for deposit/withdraw fees approximately - x -= x * Curve(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) - # Adding number of pool tokens - x += xp[MAX_COIN] - else: - # If both are from the base pool - return Curve(BASE_POOL).get_dy(base_i, base_j, dx) - - # This pool is involved only when in-pool assets are used - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D) - dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - self.fee * dy / FEE_DENOMINATOR) - - # If output is going via the metapool - if j == 0: - dy /= (rates[0] / 10**18) - else: - # j is from BasePool - # The fee is already accounted for - dy = Curve(BASE_POOL).calc_withdraw_one_coin(dy * PRECISION / rates[1], base_j) - - return dy + return StableSwapViews(factory.views_implementation()).get_dy_underlying(i, j, dx, self) @view @@ -1905,51 +1843,14 @@ def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - amount: uint256 = _amounts[i] - if _is_deposit: - new_balances[i] += amount - else: - new_balances[i] -= amount - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - if total_supply > 0: - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - new_balances[i] -= base_fee * difference / FEE_DENOMINATOR - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2 = self.get_D(xp, amp) - else: - return D1 # Take the dust if there was any + amounts: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + for i in range(MAX_COINS): + if i == N_COINS: + break + amounts[i] = _amounts[i] - diff: uint256 = 0 - if _is_deposit: - diff = D2 - D0 - else: - diff = D0 - D2 - return diff * total_supply / D0 + views: address = factory.views_implementation() + return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) @view @@ -1994,12 +1895,18 @@ def oracle(_idx: uint256) -> address: return convert(self.oracles % 2**160, address) +@view +@external +def stored_rates(i: uint256) -> uint256: + return self._stored_rates()[i] + + # --------------------------- AMM Admin Functions ---------------------------- @external def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time @@ -2022,7 +1929,7 @@ def ramp_A(_future_A: uint256, _future_time: uint256): @external def stop_ramp_A(): - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner current_A: uint256 = self._A() self.initial_A = current_A @@ -2037,7 +1944,7 @@ def stop_ramp_A(): @external def apply_new_fee(_new_fee: uint256): - assert msg.sender == Factory(self.factory).admin() + assert msg.sender == factory.admin() assert _new_fee <= MAX_FEE self.fee = _new_fee @@ -2050,7 +1957,7 @@ def set_ma_exp_time(_ma_exp_time: uint256): @notice Set the moving average window of the price oracle. @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender == factory.admin() # dev: only owner assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy new file mode 100644 index 00000000..5d82e98f --- /dev/null +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -0,0 +1,515 @@ +# @version 0.3.9 +""" +@title CurveStableSwap2NG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Auxiliary contract for Stableswap-NG containing utility methods for + integrators +""" + +interface StableSwap: + def N_COINS() -> uint256: view + def BASE_POOL() -> address: view + def BASE_N_COINS() -> uint256: view + def stored_rates(i: uint256) -> uint256: view + def balances(i: uint256) -> uint256: view + def fee() -> uint256: view + def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view + def A() -> uint256: view + def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view + def totalSupply() -> uint256: view + +interface StableSwap2: + def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view + +interface StableSwap3: + def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view + +interface StableSwap4: + def calc_token_amount(amounts: uint256[4], deposit: bool) -> uint256: view + + +A_PRECISION: constant(uint256) = 100 +MAX_COINS: constant(uint256) = 8 +PRECISION: constant(uint256) = 10 ** 18 +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 + + +# ------------------------------ Public Getters ------------------------------ + + +@view +@external +def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ + N_COINS: uint256 = StableSwap(pool).N_COINS() + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwap(pool).fee()) + x: uint256 = self.get_y(j, i, y, xp, 0, 0, N_COINS) + return (x - xp[i]) * PRECISION / rates[i] + + +@view +@external +def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + N_COINS: uint256 = StableSwap(pool).N_COINS() + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + + x: uint256 = xp[i] + (dx * rates[i] / PRECISION) + y: uint256 = self.get_y(i, j, x, xp, 0, 0, N_COINS) + dy: uint256 = xp[j] - y - 1 + fee: uint256 = StableSwap(pool).fee() * dy / FEE_DENOMINATOR + return (dy - fee) * PRECISION / rates[j] + + +@view +@external +def get_dx_underlying( + i: int128, + j: int128, + dx: uint256, + pool: address, +) -> uint256: + # TODO: Add get_dx_underlying + return 0 + + +@view +@external +def get_dy_underlying( + i: int128, + j: int128, + dx: uint256, + pool: address, +) -> uint256: + """ + @notice Calculate the current output dy given input dx on underlying + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + + N_COINS: uint256 = StableSwap(pool).N_COINS() + MAX_COIN: int128 = convert(N_COINS, int128) - 1 + BASE_POOL: address = StableSwap(pool).BASE_POOL() + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + x: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + + if i != 0: + base_i = i - MAX_COIN + meta_i = 1 + if j != 0: + base_j = j - MAX_COIN + meta_j = 1 + + if i == 0: + x = xp[i] + dx * (rates[0] / 10**18) + else: + if j == 0: + # i is from BasePool + base_n_coins: uint256 = StableSwap(pool).BASE_N_COINS() + x = self._meta_calc_token_amounts_deposit( + dx, base_i, rates[1], base_n_coins, BASE_POOL + ) + # Accounting for deposit/withdraw fees approximately + x -= x * StableSwap(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) + # Adding number of pool tokens + x += xp[MAX_COIN] + else: + # If both are from the base pool + return StableSwap(BASE_POOL).get_dy(base_i, base_j, dx) + + # This pool is involved only when in-pool assets are used + amp: uint256 = StableSwap(pool).A() * A_PRECISION + D: uint256 = self.get_D(xp, amp, N_COINS) + y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D, N_COINS) + dy: uint256 = xp[meta_j] - y - 1 + dy = (dy - StableSwap(pool).fee() * dy / FEE_DENOMINATOR) + + # If output is going via the metapool + if j == 0: + dy /= (rates[0] / 10**18) + else: + # j is from BasePool + # The fee is already accounted for + dy = StableSwap(BASE_POOL).calc_withdraw_one_coin(dy * PRECISION / rates[1], base_j) + + return dy + + +@view +@external +def calc_token_amount(_amounts: uint256[MAX_COINS], _is_deposit: bool, pool: address) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + amp: uint256 = StableSwap(pool).A() * A_PRECISION + N_COINS: uint256 = StableSwap(pool).N_COINS() + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + old_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + # Initial invariant + D0: uint256 = self.get_D(xp, amp, N_COINS) + + total_supply: uint256 = StableSwap(pool).totalSupply() + new_balances: uint256[MAX_COINS] = old_balances + for i in range(MAX_COINS): + + if i == N_COINS: + break + + amount: uint256 = _amounts[i] + if _is_deposit: + new_balances[i] += amount + else: + new_balances[i] -= amount + + # Invariant after change + for idx in range(MAX_COINS): + if idx == N_COINS: + break + xp[idx] = rates[idx] * new_balances[idx] / PRECISION + D1: uint256 = self.get_D(xp, amp, N_COINS) + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + D2: uint256 = D1 + if total_supply > 0: + # Only account for fees if we are not the first to deposit + base_fee: uint256 = StableSwap(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + for i in range(MAX_COINS): + + if i == N_COINS: + break + + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + + for idx in range(MAX_COINS): + if idx == N_COINS: + break + xp[idx] = rates[idx] * new_balances[idx] / PRECISION + + D2 = self.get_D(xp, amp, N_COINS) + else: + return D1 # Take the dust if there was any + + diff: uint256 = 0 + if _is_deposit: + diff = D2 - D0 + else: + diff = D0 - D2 + return diff * total_supply / D0 + + +@view +@external +def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> uint256: + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + + amp: uint256 = StableSwap(pool).A() * A_PRECISION + N_COINS: uint256 = StableSwap(pool).N_COINS() + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + D0: uint256 = self.get_D(xp, amp, N_COINS) + + total_supply: uint256 = StableSwap(pool).totalSupply() + D1: uint256 = D0 - _burn_amount * D0 / total_supply + new_y: uint256 = self.get_y_D(amp, i, xp, D1, N_COINS) + + base_fee: uint256 = StableSwap(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + + for j in range(MAX_COINS): + + if j == N_COINS: + break + + dx_expected: uint256 = 0 + xp_j: uint256 = xp[j] + if convert(j, int128) == i: + dx_expected = xp_j * D1 / D0 - new_y + else: + dx_expected = xp_j - xp_j * D1 / D0 + xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1, N_COINS) + dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees + dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + + return dy + + +# ----------------------------- Utility Methods ------------------------------ + + +@internal +@view +def _meta_calc_token_amounts_deposit( + dx: uint256, base_i: int128, meta_vprice: uint256, base_n_coins: uint256, base_pool: address +) -> uint256: + + if base_n_coins == 2: + + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + return StableSwap2(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + + elif base_n_coins == 3: + + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + return StableSwap3(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + + else: + + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + return StableSwap4(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + + +@internal +@pure +def newton_y(b: uint256, c: uint256, D: uint256, _y: uint256) -> uint256: + + y_prev: uint256 = 0 + y: uint256 = _y + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def get_y( + i: int128, + j: int128, + x: uint256, + xp: uint256[MAX_COINS], + _amp: uint256, + _D: uint256, + N_COINS: uint256 +) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < convert(N_COINS, int128) # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < convert(N_COINS, int128) + + amp: uint256 = _amp + D: uint256 = _D + S_: uint256 = 0 + _x: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(MAX_COINS): + + if _i == N_COINS: + break + + if convert(_i, int128) == i: + _x = x + elif convert(_i, int128) != j: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + return self.newton_y(b, c, D, y) + + +@pure +@internal +def get_D(_xp: uint256[MAX_COINS], _amp: uint256, N_COINS: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for i in range(MAX_COINS): + if i == N_COINS: + break + S += _xp[i] + + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + for i in range(255): + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + Dprev: uint256 = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@pure +@internal +def get_y_D( + A: uint256, + i: int128, + xp: uint256[MAX_COINS], + D: uint256, + N_COINS: uint256 +) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + N_COINS_128: int128 = convert(N_COINS, int128) + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(MAX_COINS): + + if _i == N_COINS: + break + + if _i != convert(i, uint256): + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + return self.newton_y(b, c, D, y) + + +@view +@internal +def _get_rates_balances_xp(pool: address, N_COINS: uint256) -> ( + uint256[MAX_COINS], uint256[MAX_COINS], uint256[MAX_COINS] +): + + rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rate: uint256 = 0 + + # Get rates and balances: + for idx in range(MAX_COINS): + if idx == N_COINS: + break + rate = StableSwap(pool).stored_rates(idx) + rates[idx] = StableSwap(pool).stored_rates(idx) + balances[idx] = StableSwap(pool).balances(idx) + + xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + for idx in range(MAX_COINS): + if idx == N_COINS: + break + xp[idx] = rates[idx] * balances[idx] / PRECISION + + return rates, balances, xp diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 1b2a685b..8d4430cc 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -42,6 +42,7 @@ def factory( fee_receiver, owner, amm_implementation_plain, + amm_implementation_meta, gauge_implementation, weth, ): @@ -56,11 +57,16 @@ def factory( # <--------------------- Functions ---------------------> -# TODO: add Factory Meta Implementation @pytest.fixture(scope="module") -def set_plain_implementations(owner, factory, pool_size, pool_type, amm_implementation_plain): +def set_meta_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): - factory.set_plain_implementations(pool_size, pool_type, amm_implementation_plain.address) + factory.set_metapool_implementations(0, amm_implementation_plain.address) + + +@pytest.fixture(scope="module") +def set_plain_implementations(owner, factory, amm_implementation_plain): + with boa.env.prank(owner): + factory.set_plain_implementations(0, amm_implementation_plain.address) @pytest.fixture(scope="module") diff --git a/tests/unitary/factory/test_meta_implementations.py b/tests/unitary/factory/test_meta_implementations.py new file mode 100644 index 00000000..b44e45a5 --- /dev/null +++ b/tests/unitary/factory/test_meta_implementations.py @@ -0,0 +1,3 @@ +def test_metapool_implmentation_set(factory, amm_implementation_meta): + + assert factory.metapool_implementations(0) == amm_implementation_meta.address From fb2da0d7a39cd8eefd555080d600ff61cf152160 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 4 Jul 2023 10:37:23 +0200 Subject: [PATCH 049/337] add plain factory tests --- tests/test_factory.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_factory.py b/tests/test_factory.py index f2332a1e..6d1e2851 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -17,6 +17,9 @@ def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receivin == swap.address ) + def test_get_n_coins(self, factory, swap, pool_tokens, pool_size, zero_address): + assert factory.get_n_coins(swap.address) == 2 + def test_get_coins(self, factory, swap, pool_tokens, pool_size, zero_address): assert factory.get_coins(swap.address) == [pt.address for pt in pool_tokens] + [zero_address] * ( MAX_COINS - pool_size @@ -55,6 +58,15 @@ def test_get_coin_indices(self, factory, swap, sending, receiving, pool_tokens): assert i == sending assert j == receiving + def test_get_implementation_address(self, factory, swap, amm_implementation_plain): + assert factory.get_implementation_address(swap.address) == amm_implementation_plain.address + + def test_is_meta(self, factory, swap): + assert factory.is_meta(swap.address) is False + + def test_fee_receiver(self, factory, swap, fee_receiver): + assert factory.get_fee_receiver(swap.address) == fee_receiver + # @pytest.mark.only_for_pool_type(1) # class TestMeta: # # TODO: replace with meta implementation From 35e6f1c8af6a8b363d21a74f7dc9b5460fa053c6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 4 Jul 2023 12:48:44 +0200 Subject: [PATCH 050/337] fix: remove self.coins since coins is immutable --- contracts/main/CurveStableSwap2NG.vy | 10 +++++----- contracts/main/CurveStableSwapMetaNG.vy | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 6fb57e26..0e86e468 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -289,7 +289,7 @@ def __init__( @external def __default__(): if msg.value > 0: - assert WETH20 in self.coins + assert WETH20 in coins @internal @@ -443,7 +443,7 @@ def _balances() -> uint256[N_COINS]: """ result: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result @@ -647,7 +647,7 @@ def remove_liquidity_one_coin( log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(self.coins[i], dy[0], _use_eth, _receiver) + self._transfer_out(coins[i], dy[0], _use_eth, _receiver) # Decrease coin[i] balance in self.stored_balances self.stored_balances[i] -= dy[0] @@ -1003,10 +1003,10 @@ def _withdraw_admin_fees(): if amounts[i] > 0: - if self.coins[i] == WETH20: + if coins[i] == WETH20: raw_call(receiver, b"", value=amounts[i]) else: - assert ERC20(self.coins[i]).transfer( + assert ERC20(coins[i]).transfer( receiver, amounts[i], default_return_value=True diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b22c228b..b53ff516 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -430,7 +430,7 @@ def _balances() -> uint256[N_COINS]: """ result: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): - result[i] = ERC20(self.coins[i]).balanceOf(self) - self.admin_balances[i] + result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result @@ -558,7 +558,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert self.is_rebasing[self.coins[i]] # dev: rebasing tokens are not supported + assert self.is_rebasing[coins[i]] # dev: rebasing tokens are not supported return self._exchange( msg.sender, i, @@ -802,7 +802,7 @@ def remove_liquidity_one_coin( log Transfer(msg.sender, empty(address), _burn_amount) - assert ERC20(self.coins[i]).transfer(_receiver, dy[0], default_return_value=True) + assert ERC20(coins[i]).transfer(_receiver, dy[0], default_return_value=True) # Decrease coin[i] balance in self.stored_balances self.stored_balances[i] -= dy[0] @@ -1059,13 +1059,13 @@ def _exchange_underlying( output_coin: address = empty(address) if i == 0: - input_coin = self.coins[0] + input_coin = coins[0] else: base_i = i - MAX_COIN # if i == 1, this reverts meta_i = 1 input_coin = BASE_COINS[base_i] if j == 0: - output_coin = self.coins[0] + output_coin = coins[0] else: base_j = j - MAX_COIN # if j == 1, this reverts meta_j = 1 @@ -1180,7 +1180,7 @@ def _withdraw_admin_fees(): @internal def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - coin_i: address = self.coins[MAX_COIN] + coin_i: address = coins[MAX_COIN] x: uint256 = ERC20(coin_i).balanceOf(self) if BASE_N_COINS == 2: From 0ecbdde558218bbe69d9a9f9a9292718caa436ce Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 4 Jul 2023 12:50:09 +0200 Subject: [PATCH 051/337] add PERMISSIONED public var --- contracts/main/CurveStableSwap2NG.vy | 1 + contracts/main/CurveStableSwapMetaNG.vy | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 0e86e468..fdc2f5c3 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -127,6 +127,7 @@ N_COINS: constant(uint256) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 IS_REBASING: immutable(bool[N_COINS]) +PERMISSIONED: public(constant(bool)) = False # Implementation contains permissionless tokens # to denote that it is a plain pool: BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b53ff516..c97d01aa 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -146,6 +146,7 @@ N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 MAX_COIN: constant(int128) = N_COINS - 1 PRECISION: constant(uint256) = 10 ** 18 +PERMISSIONED: public(constant(bool)) = False # Implementation contains permissionless tokens # token: is_rebasing flag is_rebasing: HashMap[address, bool] From e155f9e3ca9b08c89819e8aeb50f4c76bdd1ddf6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:49:53 +0200 Subject: [PATCH 052/337] add views implementation to tests --- tests/fixtures/factory.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 8d4430cc..0ce058d2 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -36,14 +36,18 @@ def amm_implementation_meta(deployer, amm_interface_meta): return amm_interface_meta.deploy_as_blueprint() +@pytest.fixture(scope="module") +def views_implementation(deployer): + with boa.env.prank(deployer): + views = boa.load("contracts/main/CurveStableSwapNGViews.vy") + return views + + @pytest.fixture(scope="module") def factory( deployer, fee_receiver, owner, - amm_implementation_plain, - amm_implementation_meta, - gauge_implementation, weth, ): with boa.env.prank(deployer): @@ -60,7 +64,7 @@ def factory( @pytest.fixture(scope="module") def set_meta_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): - factory.set_metapool_implementations(0, amm_implementation_plain.address) + factory.set_metapool_implementations(0, amm_implementation_meta.address) @pytest.fixture(scope="module") @@ -75,6 +79,12 @@ def set_gauge_implementation(owner, factory, gauge_implementation): factory.set_gauge_implementation(gauge_implementation.address) +@pytest.fixture(scope="module") +def set_views_implementation(owner, factory, views_implementation): + with boa.env.prank(owner): + factory.set_views_implementation(views_implementation.address) + + @pytest.fixture(scope="module") def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): with boa.env.prank(owner): From 78b00aa845298a57693df1e98f0aa273281afee7 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 4 Jul 2023 17:31:36 +0200 Subject: [PATCH 053/337] add pools tests init --- .github/workflows/test_pools_2.yaml | 20 +++++----- tests/fixtures/accounts.py | 38 ++++++++++++------ tests/pools/basic/test_liquidity.py | 60 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 tests/pools/basic/test_liquidity.py diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml index 92539781..a50fedd2 100644 --- a/.github/workflows/test_pools_2.yaml +++ b/.github/workflows/test_pools_2.yaml @@ -30,12 +30,14 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-interaction -# - name: Run All Token Tests 18,18 -# run: | -# source .venv/bin/activate -# pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto -# -# - name: Run Plain Tests 18,6 -# run: | -# source .venv/bin/activate -# pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto + - name: Run All Token Tests 18,18 + run: | + source .venv/bin/activate + pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto + + - name: Run Plain Tests 18,6 + run: | + source .venv/bin/activate + pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto + +# TODO: add meta diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index d6ccd260..da5ce135 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -62,29 +62,43 @@ def accounts(bob, charlie, dave, erin, frank): # <--------------------- Functions ---------------------> +def mint_account(account, pool_tokens, initial_amounts): + mint_for_testing(account, 10**18, None, True) + for pool_token, amount in zip(pool_tokens, initial_amounts): + mint_for_testing(account, amount, pool_token, False) + + +def approve_account(account, pool_tokens, swap): + for pool_token in pool_tokens: + with boa.env.prank(account): + pool_token.approve(swap.address, 2**256 - 1) + + @pytest.fixture(scope="module") def mint_owner(owner, pool_tokens, initial_amounts): - mint_for_testing(owner, 10**18, None, True) - for pool_token, amount in zip(pool_tokens, initial_amounts): - mint_for_testing(owner, amount, pool_token, False) + mint_account(owner, pool_tokens, initial_amounts) @pytest.fixture(scope="module") def approve_owner(owner, pool_tokens, swap): - for pool_token in pool_tokens: - with boa.env.prank(owner): - pool_token.approve(swap.address, 2**256 - 1) + approve_account(owner, pool_tokens, swap) @pytest.fixture(scope="module") def mint_alice(alice, pool_tokens, initial_amounts): - mint_for_testing(alice, 10**18, None, True) - for pool_token, amount in zip(pool_tokens, initial_amounts): - mint_for_testing(alice, amount, pool_token, False) + mint_account(alice, pool_tokens, initial_amounts) @pytest.fixture(scope="module") def approve_alice(alice, pool_tokens, swap): - for pool_token in pool_tokens: - with boa.env.prank(alice): - pool_token.approve(swap.address, 2**256 - 1) + approve_account(alice, pool_tokens, swap) + + +@pytest.fixture(scope="module") +def mint_bob(bob, pool_tokens, initial_amounts): + mint_account(bob, pool_tokens, initial_amounts) + + +@pytest.fixture(scope="module") +def approve_bob(bob, pool_tokens, swap): + approve_account(bob, pool_tokens, swap) diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py new file mode 100644 index 00000000..67300510 --- /dev/null +++ b/tests/pools/basic/test_liquidity.py @@ -0,0 +1,60 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + +DEPOSIT_AMOUNT = 500_000 + + +class TestLiquidityMethods: + @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") + class TestAddLiquidity: + def test_add_liquidity(self, bob, swap, pool_tokens, deposit_amounts, initial_amounts): + swap.add_liquidity(deposit_amounts, 0, sender=bob) + + for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): + assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap) == deposit_amounts[i] * 2 + + ideal = len(pool_tokens) * DEPOSIT_AMOUNT * 10**18 + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 + + @pytest.mark.parametrize("idx", (0, 1)) + def test_add_one_coin(self, bob, swap, pool_tokens, deposit_amounts, initial_amounts, idx): + amounts = [0] * len(pool_tokens) + amounts[idx] = deposit_amounts[idx] + + swap.add_liquidity(amounts, 0, sender=bob) + + for i, pool_token in enumerate(pool_tokens): + assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] + assert pool_token.balanceOf(swap) == deposit_amounts[i] + amounts[i] + + difference = abs(swap.balanceOf(bob) - (10**18 * DEPOSIT_AMOUNT)) + assert difference / (10**18 * DEPOSIT_AMOUNT) < 0.01 + + def test_insufficient_balance(self, charlie, swap, decimals): + amounts = [(10**i) for i in decimals] + + with boa.reverts(): # invalid approval or balance + swap.add_liquidity(amounts, 0, sender=charlie) + + def test_min_amount_too_high(self, bob, swap, deposit_amounts, pool_size): + with boa.reverts(): + swap.add_liquidity(deposit_amounts, pool_size * DEPOSIT_AMOUNT * 10**18 + 1, sender=bob) + + def test_event(self, bob, swap, deposit_amounts): + _, events = call_returning_result_and_logs(swap, "add_liquidity", deposit_amounts, 0, sender=bob) + + assert len(events) == 4 # Transfer token1, Transfer token2, Transfer LP, Add liquidity + assert ( + repr(events[3]) == f"AddLiquidity(provider={bob}, token_amounts={deposit_amounts}, fees=[0, 0], " + f"invariant={2_000_000 * 10 ** 18}, token_supply={swap.totalSupply()})" + ) + + # TODO: fix this + # @pytest.mark.parametrize("use_eth", (True, False)) + # def test_send_eth(self, bob, swap, deposit_amounts, use_eth): + # with boa.reverts(): + # swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=1) From 7329aaa05040d16dfea30fd6fdda1e73f65979f8 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 4 Jul 2023 18:22:37 +0200 Subject: [PATCH 054/337] fix fixtures --- tests/fixtures/factory.py | 23 +++++++------------ .../factory/test_meta_implementations.py | 3 --- 2 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 tests/unitary/factory/test_meta_implementations.py diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index ceea77f8..881f7565 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -14,9 +14,8 @@ def gauge_implementation(deployer, gauge_interface): @pytest.fixture(scope="module") -def amm_interface_plain(pool_size): - if pool_size == 2: - return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") +def amm_interface_plain(): + return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") @pytest.fixture(scope="module") @@ -39,8 +38,7 @@ def amm_implementation_meta(deployer, amm_interface_meta): @pytest.fixture(scope="module") def views_implementation(deployer): with boa.env.prank(deployer): - views = boa.load("contracts/main/CurveStableSwapNGViews.vy") - return views + return boa.load("contracts/main/CurveStableSwapNGViews.vy") @pytest.fixture(scope="module") @@ -62,26 +60,22 @@ def factory( # <--------------------- Functions ---------------------> @pytest.fixture(scope="module") -def set_meta_implementations(owner, factory, amm_implementation_meta): +def set_plain_implementations(owner, factory, amm_implementation_plain): with boa.env.prank(owner): - factory.set_metapool_implementations(0, amm_implementation_meta.address) + factory.set_plain_implementations(0, amm_implementation_plain.address) @pytest.fixture(scope="module") -def set_plain_implementations(owner, factory, amm_implementation_plain): +def set_meta_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): - factory.set_plain_implementations(0, amm_implementation_plain.address) + factory.set_metapool_implementations(0, amm_implementation_meta.address) -# <--------------------- Metapool configuration ---------------------> @pytest.fixture(scope="module") -def set_meta_implementations( +def add_base_pool( owner, fee_receiver, factory, - pool_size, - pool_type, - amm_implementation_meta, base_pool, base_pool_lp_token, base_pool_tokens, @@ -98,7 +92,6 @@ def set_meta_implementations( [False] * len(base_pool_tokens), [zero_address] * len((base_pool_tokens)), ) - factory.set_metapool_implementations(base_pool.address, [amm_implementation_meta.address] + [zero_address] * 9) @pytest.fixture(scope="module") diff --git a/tests/unitary/factory/test_meta_implementations.py b/tests/unitary/factory/test_meta_implementations.py deleted file mode 100644 index b44e45a5..00000000 --- a/tests/unitary/factory/test_meta_implementations.py +++ /dev/null @@ -1,3 +0,0 @@ -def test_metapool_implmentation_set(factory, amm_implementation_meta): - - assert factory.metapool_implementations(0) == amm_implementation_meta.address From 1b45c54b7758d88f3928c4f0a3ae64c4f9b433a3 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 4 Jul 2023 20:45:24 +0200 Subject: [PATCH 055/337] uncomment test --- tests/pools/basic/test_liquidity.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 67300510..18e4b01a 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -54,7 +54,11 @@ def test_event(self, bob, swap, deposit_amounts): ) # TODO: fix this - # @pytest.mark.parametrize("use_eth", (True, False)) - # def test_send_eth(self, bob, swap, deposit_amounts, use_eth): - # with boa.reverts(): - # swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=1) + @pytest.mark.parametrize("use_eth", (True, False)) + def test_send_eth(self, bob, swap, deposit_amounts, use_eth, weth, pool_tokens): + assert swap.WETH20() == weth.address + assert weth.address not in [t.address for t in pool_tokens] + assert swap.coins(0) == pool_tokens[0].address + assert swap.coins(1) == pool_tokens[1].address + with boa.reverts(): + swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=1) From bc10fc4bb4a91756cd408ad2459e80c415f0cd98 Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 5 Jul 2023 14:16:48 +0200 Subject: [PATCH 056/337] fixes --- contracts/main/CurveStableSwap2NG.vy | 23 ++++++++++++++-- tests/fixtures/accounts.py | 16 +++++------ tests/fixtures/constants.py | 5 ++++ tests/fixtures/pools.py | 7 ++++- tests/pools/basic/test_liquidity.py | 40 ++++++++++++++++++---------- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index fdc2f5c3..745026c0 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -444,7 +444,12 @@ def _balances() -> uint256[N_COINS]: """ result: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): - result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + + if coins[i] == WETH20: + result[i] = self.balance - self.admin_balances[i] + + else: + result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result @@ -887,6 +892,7 @@ def _add_liquidity( if expect_optimistic_transfer: dx: uint256 = 0 + assert mvalue == 0 # dev: not supported for i in range(N_COINS): @@ -912,9 +918,18 @@ def _add_liquidity( if coins[i] == WETH20: + _amount: uint256 = amounts[i] + + if use_eth: + assert _amount == 0 # dev: only eth, no WETH should be sent + _amount = mvalue + + else: + assert mvalue == 0 # dev: only WETH should be sent + new_balances[i] += self._transfer_in( coins[i], - amounts[i], + _amount, 0, mvalue, empty(address), @@ -926,6 +941,10 @@ def _add_liquidity( else: + # fix: this is workaround + if coins[0] != WETH20: + assert mvalue == 0 + new_balances[i] += self._transfer_in( coins[i], amounts[i], diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index da5ce135..10f8a650 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -62,8 +62,8 @@ def accounts(bob, charlie, dave, erin, frank): # <--------------------- Functions ---------------------> -def mint_account(account, pool_tokens, initial_amounts): - mint_for_testing(account, 10**18, None, True) +def mint_account(account, pool_tokens, initial_balance, initial_amounts): + mint_for_testing(account, initial_balance, None, True) for pool_token, amount in zip(pool_tokens, initial_amounts): mint_for_testing(account, amount, pool_token, False) @@ -75,8 +75,8 @@ def approve_account(account, pool_tokens, swap): @pytest.fixture(scope="module") -def mint_owner(owner, pool_tokens, initial_amounts): - mint_account(owner, pool_tokens, initial_amounts) +def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): + mint_account(owner, pool_tokens, initial_balance, initial_amounts) @pytest.fixture(scope="module") @@ -85,8 +85,8 @@ def approve_owner(owner, pool_tokens, swap): @pytest.fixture(scope="module") -def mint_alice(alice, pool_tokens, initial_amounts): - mint_account(alice, pool_tokens, initial_amounts) +def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): + mint_account(alice, pool_tokens, initial_balance, initial_amounts) @pytest.fixture(scope="module") @@ -95,8 +95,8 @@ def approve_alice(alice, pool_tokens, swap): @pytest.fixture(scope="module") -def mint_bob(bob, pool_tokens, initial_amounts): - mint_account(bob, pool_tokens, initial_amounts) +def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): + mint_account(bob, pool_tokens, initial_balance, initial_amounts) @pytest.fixture(scope="module") diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 138f2a0f..1ad936aa 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -3,6 +3,11 @@ INITIAL_AMOUNT = 1_000_000 +@pytest.fixture(scope="module") +def initial_balance() -> int: + return INITIAL_AMOUNT * 10**18 + + @pytest.fixture(scope="module") def initial_amounts(decimals: list[int]) -> list[int]: return [INITIAL_AMOUNT * 10**precision for precision in decimals] diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index f050700c..4560a142 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -121,7 +121,7 @@ def swap( # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): with boa.env.prank(deployer): base_pool = boa.load( @@ -148,6 +148,11 @@ def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base # <--------------------- Functions ---------------------> +@pytest.fixture(scope="module") +def is_eth_pool(pool_tokens, weth): + return weth in pool_tokens + + @pytest.fixture(scope="module") def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): with boa.env.prank(owner): diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 18e4b01a..1b3c8417 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -9,27 +9,42 @@ class TestLiquidityMethods: @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") class TestAddLiquidity: - def test_add_liquidity(self, bob, swap, pool_tokens, deposit_amounts, initial_amounts): - swap.add_liquidity(deposit_amounts, 0, sender=bob) + @pytest.mark.parametrize("use_eth", (True, False), scope="session") + def test_add_liquidity( + self, bob, swap, is_eth_pool, pool_tokens, deposit_amounts, initial_balance, initial_amounts, use_eth + ): + value = deposit_amounts[0] if is_eth_pool and use_eth else 0 + deposit_amounts[0] = 0 if is_eth_pool and use_eth else deposit_amounts[0] + swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=value) for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): + # assert boa.env.get_balance(bob) == initial_balance - value + # assert boa.env.get_balance(swap.address) == deposit_amounts[i] + value + assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap) == deposit_amounts[i] * 2 + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 ideal = len(pool_tokens) * DEPOSIT_AMOUNT * 10**18 assert abs(swap.balanceOf(bob) - ideal) <= 1 assert abs(swap.totalSupply() - ideal * 2) <= 2 @pytest.mark.parametrize("idx", (0, 1)) - def test_add_one_coin(self, bob, swap, pool_tokens, deposit_amounts, initial_amounts, idx): + @pytest.mark.parametrize("use_eth", (True, False)) + def test_add_one_coin( + self, bob, swap, pool_tokens, is_eth_pool, deposit_amounts, initial_amounts, idx, weth, use_eth + ): amounts = [0] * len(pool_tokens) amounts[idx] = deposit_amounts[idx] swap.add_liquidity(amounts, 0, sender=bob) for i, pool_token in enumerate(pool_tokens): - assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] - assert pool_token.balanceOf(swap) == deposit_amounts[i] + amounts[i] + if pool_token == weth: + assert boa.env.get_balance(bob) == initial_amounts[i] - amounts[i] + assert boa.env.get_balance(swap) == deposit_amounts[i] + amounts[i] + else: + assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] + assert pool_token.balanceOf(swap) == deposit_amounts[i] + amounts[i] difference = abs(swap.balanceOf(bob) - (10**18 * DEPOSIT_AMOUNT)) assert difference / (10**18 * DEPOSIT_AMOUNT) < 0.01 @@ -54,11 +69,8 @@ def test_event(self, bob, swap, deposit_amounts): ) # TODO: fix this - @pytest.mark.parametrize("use_eth", (True, False)) - def test_send_eth(self, bob, swap, deposit_amounts, use_eth, weth, pool_tokens): - assert swap.WETH20() == weth.address - assert weth.address not in [t.address for t in pool_tokens] - assert swap.coins(0) == pool_tokens[0].address - assert swap.coins(1) == pool_tokens[1].address - with boa.reverts(): - swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=1) + # + # def test_send_eth(self, bob, swap, deposit_amounts, use_eth): + # + # # with boa.reverts(): + # swap.add_liquidity(deposit_amounts, 0, True, sender=bob, value=1) From 37836fc82c39c59d4b5d640d3328d95021abbe03 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:51:26 +0200 Subject: [PATCH 057/337] fix: use weth instead of eth in pool --- contracts/main/CurveStableSwap2NG.vy | 338 +++++++++++---------------- 1 file changed, 131 insertions(+), 207 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 745026c0..a150ac08 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -295,7 +295,7 @@ def __default__(): @internal def _transfer_in( - coin: address, + coin_idx: int128, dx: uint256, dy: uint256, mvalue: uint256, @@ -304,10 +304,10 @@ def _transfer_in( sender: address, receiver: address, use_eth: bool, + expect_optimistic_transfer: bool, ) -> uint256: """ - @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig` - if it is not empty. + @notice Contains all logic to handle ERC20 or native token transfers @dev The callback sig must have the following args: sender: address receiver: address @@ -327,51 +327,55 @@ def _transfer_in( @params sender address to transfer `_coin` from. @params receiver address to transfer `_coin` to. @params use_eth True if the transfer is ETH, False otherwise. + @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ - _dx: uint256 = dx + coin: address = coins[coin_idx] + is_rebasing: bool = True in IS_REBASING if use_eth and coin == WETH20: # <----------- Pool receives native token. - assert mvalue == _dx # dev: incorrect eth amount + assert mvalue == dx # dev: incorrect eth amount + WETH(WETH20).deposit(value=dx) # <--- deposit incoming native token. - else: # <------- Pool receives wrapped native token and not native token. + return dx - assert mvalue == 0 # dev: nonzero eth amount + if expect_optimistic_transfer: # <---- Pool expects _dx of coin transferred to it by the user. - initial_x: uint256 = ERC20(coin).balanceOf(self) + assert not is_rebasing - # --------------------- Start Callback Handling ---------------------- + assert ERC20(coin).balanceOf(self) - self.stored_balances[coin_idx] == dx, "Pool did not receive tokens for swap" - if callback_sig == empty(bytes32): + return dx - assert ERC20(coin).transferFrom( - sender, self, _dx, default_return_value=True - ) + # ---- For normal ERC20 token handling: with callbacks! - else: + assert mvalue == 0 # dev: msg.value must be zero + + _dx: uint256 = ERC20(coin).balanceOf(self) - raw_call( + if callback_sig != empty(bytes32): # <---- ERC20 token transfer is handled by callback. + + raw_call( callbacker, concat( slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coin, _dx, dy) + _abi_encode(sender, receiver, coin, dx, dy) ) ) - # If the coin is a fee-on-transfer token, transferring `_dx` amount can - # result in the pool receiving slightly less amount. So: recalculate dx + else: # <--- Pool calls ERC20.transferFrom if msg.sender has given approval. - _dx = ERC20(coin).balanceOf(self) - initial_x + assert ERC20(coin).transferFrom( + sender, self, dx, default_return_value=True + ) + # Check if the pool received tokens at all. _dx == dx if coins are not rebasing + _dx = ERC20(coin).balanceOf(self) - _dx + if is_rebasing: assert _dx > 0 # dev: pool received 0 tokens + return _dx - # -------------------- End Callback Handling ------------------------- - - if coin == WETH20: - WETH(WETH20).withdraw(_dx) # <--------- if WETH was transferred in - # previous step and `not use_eth`, withdraw WETH to ETH. - - # Return _dx so it can be used by `_exchange` and `add_liquidity`. + assert dx == _dx return _dx @@ -390,10 +394,11 @@ def _transfer_out( """ if use_eth and _coin == WETH20: + + WETH(WETH20).withdraw(_amount) raw_call(receiver, b"", value=_amount) + else: - if _coin == WETH20: - WETH(WETH20).deposit(value=_amount) assert ERC20(_coin).transfer( receiver, _amount, default_return_value=True @@ -444,12 +449,7 @@ def _balances() -> uint256[N_COINS]: """ result: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS): - - if coins[i] == WETH20: - result[i] = self.balance - self.admin_balances[i] - - else: - result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result @@ -615,15 +615,89 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - return self._add_liquidity( - msg.sender, - _amounts, - _min_mint_amount, - _use_eth, - _receiver, - msg.value, - False, # <--------------------- Does not expect optimistic transfers. - ) + amp: uint256 = self._A() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.totalSupply + new_balances: uint256[N_COINS] = old_balances + + # -------------------------- Do Transfers In ----------------------------- + + for i in range(N_COINS_128): + + if _amounts[i] > 0: + + new_balances[i] += self._transfer_in( + i, + _amounts[i], + 0, + msg.value, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + _use_eth, + False, # expect_optimistic_transfer + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # Add incoming balance + self._increase_balances(new_balances) + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: uint256[N_COINS] = empty(uint256[N_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS): + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[_receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), _receiver, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount @external @@ -795,7 +869,7 @@ def _exchange( receiver: address, callbacker: address, callback_sig: bytes32, - expect_optimistic_transfer: bool = False + expect_optimistic_transfer: bool ) -> uint256: assert i != j # dev: coin index out of range @@ -807,22 +881,19 @@ def _exchange( # --------------------------- Do Transfer in ----------------------------- - dx: uint256 = 0 - - if expect_optimistic_transfer: - - # This branch is never reached for rebasing tokens - dx = ERC20(coins[i]).balanceOf(self) - self.stored_balances[i] - assert dx == _dx, "Pool did not receive tokens for swap" - - else: - - # `dx` is whatever the pool received after ERC20 transfer: - dx = self._transfer_in( - coins[i], _dx, _min_dy, mvalue, - callbacker, callback_sig, - sender, receiver, use_eth - ) + # `dx` is whatever the pool received after ERC20 transfer: + dx: uint256 = self._transfer_in( + i, + _dx, + _min_dy, + mvalue, + callbacker, + callback_sig, + sender, + receiver, + use_eth, + expect_optimistic_transfer + ) # Update stored balances self.stored_balances[i] += dx @@ -866,153 +937,6 @@ def _exchange( return dy -@internal -def _add_liquidity( - sender: address, - amounts: uint256[N_COINS], - min_mint_amount: uint256, - use_eth: bool, - receiver: address, - mvalue: uint256, - expect_optimistic_transfer: bool = False, -) -> uint256: - - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - - # -------------------------- Do Transfers In ----------------------------- - - if expect_optimistic_transfer: - - dx: uint256 = 0 - assert mvalue == 0 # dev: not supported - - for i in range(N_COINS): - - if amounts[i] > 0: - - # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] - - assert dx == amounts[i], "Pool did not receive tokens for adding liquidity" - - new_balances[i] += dx - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - else: - - for i in range(N_COINS): - - if amounts[i] > 0: - - if coins[i] == WETH20: - - _amount: uint256 = amounts[i] - - if use_eth: - assert _amount == 0 # dev: only eth, no WETH should be sent - _amount = mvalue - - else: - assert mvalue == 0 # dev: only WETH should be sent - - new_balances[i] += self._transfer_in( - coins[i], - _amount, - 0, - mvalue, - empty(address), - empty(bytes32), - sender, - empty(address), - use_eth - ) - - else: - - # fix: this is workaround - if coins[0] != WETH20: - assert mvalue == 0 - - new_balances[i] += self._transfer_in( - coins[i], - amounts[i], - 0, - 0, - empty(address), - empty(bytes32), - sender, - empty(address), - False - ) - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - # Add incoming balance - self._increase_balances(new_balances) - - # ------------------------------------------------------------------------ - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - - if total_supply > 0: - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) - - else: - - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[receiver] += mint_amount - self.totalSupply = total_supply - log Transfer(empty(address), receiver, mint_amount) - - log AddLiquidity(sender, amounts, fees, D1, total_supply) - - return mint_amount - - @internal def _withdraw_admin_fees(): From 0ebeaa72b4ac8b1258b83d5f48abdad6dd63e974 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:52:50 +0200 Subject: [PATCH 058/337] collect admin fees in weth --- contracts/main/CurveStableSwap2NG.vy | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index a150ac08..d1353279 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -947,14 +947,11 @@ def _withdraw_admin_fees(): if amounts[i] > 0: - if coins[i] == WETH20: - raw_call(receiver, b"", value=amounts[i]) - else: - assert ERC20(coins[i]).transfer( - receiver, - amounts[i], - default_return_value=True - ) + assert ERC20(coins[i]).transfer( + receiver, + amounts[i], + default_return_value=True + ) self.admin_balances = empty(uint256[N_COINS]) # Reduce stored balances: From 49788ca0da93d9f7ba3fc270da6683db847c409d Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 5 Jul 2023 16:07:08 +0200 Subject: [PATCH 059/337] fix asser --- contracts/main/CurveStableSwap2NG.vy | 3 ++- tests/pools/basic/test_liquidity.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index d1353279..8f5e13db 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -349,7 +349,8 @@ def _transfer_in( # ---- For normal ERC20 token handling: with callbacks! - assert mvalue == 0 # dev: msg.value must be zero + if coins[0] != WETH20: + assert mvalue == 0 # dev: msg.value must be zero _dx: uint256 = ERC20(coin).balanceOf(self) diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 1b3c8417..95c7ebae 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -18,9 +18,6 @@ def test_add_liquidity( swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=value) for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): - # assert boa.env.get_balance(bob) == initial_balance - value - # assert boa.env.get_balance(swap.address) == deposit_amounts[i] + value - assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 From bcc403a11f4d82fb33304afdf5b2627dbaf03960 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:22:21 +0200 Subject: [PATCH 060/337] remove redundant weth check --- contracts/main/CurveStableSwap2NG.vy | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 8f5e13db..62202322 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -349,9 +349,6 @@ def _transfer_in( # ---- For normal ERC20 token handling: with callbacks! - if coins[0] != WETH20: - assert mvalue == 0 # dev: msg.value must be zero - _dx: uint256 = ERC20(coin).balanceOf(self) if callback_sig != empty(bytes32): # <---- ERC20 token transfer is handled by callback. From 674e4cc537e6dc056d2e014aa3eade28a684e0a1 Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 6 Jul 2023 07:59:40 +0200 Subject: [PATCH 061/337] concept --- contracts/main/CurveStableSwapFactoryNG.vy | 32 ++++++++++------------ tests/fixtures/pools.py | 12 ++++---- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index dc5099b8..66e99682 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -13,7 +13,7 @@ struct PoolArray: coins: address[MAX_COINS] decimals: uint256[MAX_COINS] n_coins: uint256 - asset_type: uint256 + asset_types: uint256[MAX_COINS] struct BasePoolArray: lp_token: address @@ -22,7 +22,7 @@ struct BasePoolArray: is_rebasing: bool[MAX_COINS] decimals: uint256 n_coins: uint256 - asset_type: uint256 + asset_types: uint256[MAX_COINS] interface AddressProvider: @@ -449,7 +449,7 @@ def is_meta(_pool: address) -> bool: @view @external -def get_pool_asset_type(_pool: address) -> uint256: +def get_pool_asset_types(_pool: address) -> uint256[MAX_COINS]: """ @notice Query the asset type of `_pool` @dev 0 = USD, 1 = ETH, 2 = BTC, 3 = Other @@ -458,9 +458,9 @@ def get_pool_asset_type(_pool: address) -> uint256: """ base_pool: address = self.pool_data[_pool].base_pool if base_pool == empty(address): - return self.pool_data[_pool].asset_type + return self.pool_data[_pool].asset_types else: - return self.base_pool_data[base_pool].asset_type + return self.base_pool_data[base_pool].asset_types @view @@ -485,7 +485,7 @@ def deploy_plain_pool( _ma_exp_time: uint256, _method_ids: bytes4[MAX_COINS] = empty(bytes4[MAX_COINS]), _oracles: address[MAX_COINS] = empty(address[MAX_COINS]), - _asset_type: uint256 = 0, + _asset_types: uint256[MAX_COINS] = empty(uint256[MAX_COINS]), _implementation_idx: uint256 = 0, _is_rebasing: bool[MAX_COINS] = empty(bool[MAX_COINS]) ) -> address: @@ -510,8 +510,8 @@ def deploy_plain_pool( of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _asset_type Asset type for pool, as an integer - 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _asset_types Asset types for pool, as an integer + 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING @param _implementation_idx Index of the implementation to use. All possible implementations for a pool of N_COINS can be publicly accessed via `plain_implementations(N_COINS)` @@ -570,8 +570,7 @@ def deploy_plain_pool( self.pool_data[pool].n_coins = n_coins self.pool_data[pool].base_pool = empty(address) self.pool_data[pool].implementation = implementation - if _asset_type != 0: - self.pool_data[pool].asset_type = _asset_type + self.pool_data[pool].asset_types = _asset_types for i in range(MAX_COINS): coin: address = _coins[i] @@ -735,7 +734,7 @@ def add_base_pool( _base_lp_token: address, _fee_receiver: address, _coins: address[MAX_COINS], - _asset_type: uint256, + _asset_types: uint256[MAX_COINS], _n_coins: uint256, _is_rebasing: bool[MAX_COINS], ): @@ -744,7 +743,7 @@ def add_base_pool( @dev Only callable by admin @param _base_pool Pool address to add @param _fee_receiver Admin fee receiver address for metapools using this base pool - @param _asset_type Asset type for pool, as an integer 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @param _asset_types Asset type for pool, as an integer 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing """ assert msg.sender == self.admin # dev: admin-only function @@ -758,8 +757,7 @@ def add_base_pool( self.base_pool_data[_base_pool].lp_token = _base_lp_token self.base_pool_data[_base_pool].n_coins = _n_coins self.base_pool_data[_base_pool].fee_receiver = _fee_receiver - if _asset_type != 0: - self.base_pool_data[_base_pool].asset_type = _asset_type + self.base_pool_data[_base_pool].asset_types = _asset_types decimals: uint256 = 0 coins: address[MAX_COINS] = _coins @@ -830,17 +828,17 @@ def set_views_implementation(_views_implementation: address): @external -def batch_set_pool_asset_type(_pools: address[32], _asset_types: uint256[32]): +def batch_set_pool_asset_types(_pools: address[32], _asset_types: uint256[MAX_COINS][32]): """ @notice Batch set the asset type for factory pools @dev Used to modify asset types that were set incorrectly at deployment """ - assert msg.sender in [self.admin] # dev: admin-only function + assert msg.sender == self.admin # dev: admin-only function for i in range(32): if _pools[i] == empty(address): break - self.pool_data[_pools[i]].asset_type = _asset_types[i] + self.pool_data[_pools[i]].asset_types = _asset_types[i] @external diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 4560a142..d533ca43 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -24,28 +24,28 @@ def swap( fee = 1000000 method_ids = [bytes(b"")] * 8 oracles = [zero_address] * 8 - asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + asset_type = [] is_rebasing = [False] * 8 for i, t in enumerate(pool_token_types): if t == 0: A = 2000 fee = 1000000 - asset_type = 0 + asset_type.append(0) elif t == 1: A = 1000 fee = 3000000 - asset_type = 1 + asset_type.append(1) elif t == 2: A = 1000 fee = 3000000 - asset_type = 1 + asset_type.append(2) method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address elif t == 3: A = 500 fee = 4000000 - asset_type = 1 + asset_type.append(3) is_rebasing[i] = True with boa.env.prank(deployer): @@ -58,7 +58,7 @@ def swap( 866, method_ids, oracles, - asset_type, + asset_type + [0] * (8 - len(asset_type)), 0, is_rebasing, ) From 9f1bb54c6938166a15cab504afe89c16870b621b Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 6 Jul 2023 11:46:07 +0200 Subject: [PATCH 062/337] add meta tests --- contracts/main/CurveStableSwapFactoryNG.vy | 1 + contracts/main/CurveStableSwapMetaNG.vy | 2 +- tests/fixtures/factory.py | 5 +- tests/fixtures/pools.py | 4 +- tests/test_factory.py | 102 +++++++++------------ 5 files changed, 48 insertions(+), 66 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index dc5099b8..772a34d8 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -637,6 +637,7 @@ def deploy_metapool( """ assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" + assert self.base_pool_data[_base_pool].coins[0] != empty(address), "Base pool is not added" implementation: address = self.metapool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c97d01aa..c7b9f4d0 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -230,7 +230,7 @@ def __init__( _ma_exp_time: uint256, _method_id: bytes4, _oracle: address, - _is_rebasing: bool[MAX_COINS], # [_coin, False, base_coin_0, base_coin_1, ...] + _is_rebasing: bool[MAX_COINS], # [_coin, base_coin_0, base_coin_1, ...] _base_pool: address, _base_lp_token: address, _base_coins: address[MAX_COINS], # base pool can have maximally (MAX_COINS - 1) coins diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 881f7565..8d26d1ee 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -86,11 +86,10 @@ def add_base_pool( base_pool.address, base_pool_lp_token.address, fee_receiver, - [t.address for t in base_pool_tokens], + [t.address for t in base_pool_tokens] + [zero_address] * (8 - len(base_pool_tokens)), 0, len(base_pool_tokens), - [False] * len(base_pool_tokens), - [zero_address] * len((base_pool_tokens)), + [False] * 8, ) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 4560a142..5c4feba0 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -68,6 +68,7 @@ def swap( base_pool = request.getfixturevalue("base_pool") underlying_tokens = request.getfixturevalue("underlying_tokens") amm_interface_meta = request.getfixturevalue("amm_interface_meta") + _ = request.getfixturevalue("add_base_pool") _ = request.getfixturevalue("set_meta_implementations") A = 2000 @@ -109,7 +110,6 @@ def swap( 866, method_id, oracle, - asset_type, 0, is_rebasing, ) @@ -121,7 +121,7 @@ def swap( # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): with boa.env.prank(deployer): base_pool = boa.load( diff --git a/tests/test_factory.py b/tests/test_factory.py index 6d1e2851..43608f65 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -1,12 +1,10 @@ -# import itertools +import itertools import boa import pytest MAX_COINS = 8 -# TODO: ADD meta - class TestFactory: class TestBasic: @@ -67,63 +65,47 @@ def test_is_meta(self, factory, swap): def test_fee_receiver(self, factory, swap, fee_receiver): assert factory.get_fee_receiver(swap.address) == fee_receiver - # @pytest.mark.only_for_pool_type(1) - # class TestMeta: - # # TODO: replace with meta implementation - # @pytest.fixture - # def new_factory_setup( - # self, - # new_factory, - # amm_implementation_plain, - # pool_size, - # alice, - # fee_receiver, - # ): - # with boa.env.prank(alice): - # new_factory.set_plain_implementations(pool_size, 0, amm_implementation_plain.address) - # - # def test_factory(self, factory, swap): - # assert factory.get_meta_n_coins(swap) == [2, 4] - # - # def test_get_underlying_coins(self, factory, swap, underlying_coins, pool_size, zero_address): - # assert factory.get_underlying_coins(swap) == underlying_coins + [zero_address] * ( - # 4 - pool_size - # ) - # - # def test_get_underlying_decimals(self, factory, swap, underlying_decimals): - # assert factory.get_underlying_decimals(swap) == underlying_decimals + [0] * ( - # 4 - len(underlying_decimals) - # ) - # - # @pytest.mark.parametrize("idx", range(1, 4)) - # def test_find_pool_for_coins_underlying(self, factory, swap, underlying_coins, idx): - # assert factory.find_pool_for_coins(underlying_coins[0], underlying_coins[idx]) == swap - # assert factory.find_pool_for_coins(underlying_coins[idx], underlying_coins[0]) == swap - # - # def test_get_metapool_rates(self, factory, swap, base_pool): - # assert factory.get_metapool_rates(swap) == [10 ** 18, base_pool.get_virtual_price()] - # - # @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) - # def test_find_pool_underlying_base_pool_only(self, factory, underlying_coins, sending, receiving, zero_address): - # assert ( - # factory.find_pool_for_coins(underlying_coins[sending], underlying_coins[receiving]) - # == zero_address - # ) - # - # @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) - # def test_get_coin_indices_underlying(factory, swap, sending, receiving, underlying_coins): - # i, j, is_underlying = factory.get_coin_indices( - # swap, underlying_coins[sending], underlying_coins[receiving] - # ) - # assert i == sending - # assert j == receiving - # assert is_underlying is False - # - # @pytest.mark.parametrize("idx", range(1, 4)) - # def test_get_coin_indices_reverts(self, factory, swap, base_lp_token, underlying_coins, idx): - # with boa.reverts(): - # factory.get_coin_indices(swap, base_lp_token, underlying_coins[idx]) - # + @pytest.mark.only_for_pool_type(1) + class TestMeta: + def test_factory(self, factory, swap): + assert factory.get_meta_n_coins(swap) == [2, 4] + + def test_get_underlying_coins(self, factory, swap, underlying_tokens, pool_size, zero_address): + assert factory.get_underlying_coins(swap) == underlying_tokens + [zero_address] * (MAX_COINS - pool_size) + + def test_get_underlying_decimals(self, factory, swap, base_pool_decimals): + assert factory.get_underlying_decimals(swap) == base_pool_decimals + [0] * ( + MAX_COINS - len(base_pool_decimals) + ) + + @pytest.mark.parametrize("idx", range(1, 4)) + def test_find_pool_for_coins_underlying(self, factory, swap, underlying_coins, idx): + assert factory.find_pool_for_coins(underlying_coins[0], underlying_coins[idx]) == swap + assert factory.find_pool_for_coins(underlying_coins[idx], underlying_coins[0]) == swap + + def test_get_metapool_rates(self, factory, swap, base_pool): + assert factory.get_metapool_rates(swap) == [10**18, base_pool.get_virtual_price()] + + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) + def test_find_pool_underlying_base_pool_only( + self, factory, underlying_tokens, sending, receiving, zero_address + ): + assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address + + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) + def test_get_coin_indices_underlying(self, factory, swap, sending, receiving, underlying_tokens): + i, j, is_underlying = factory.get_coin_indices( + swap, underlying_tokens[sending], underlying_tokens[receiving] + ) + assert i == sending + assert j == receiving + assert is_underlying is False + + @pytest.mark.parametrize("idx", range(1, 4)) + def test_get_coin_indices_reverts(self, factory, swap, base_lp_token, underlying_tokens, idx): + with boa.reverts(): + factory.get_coin_indices(swap, base_lp_token, underlying_tokens[idx]) + # @pytest.mark.usefixtures("forked_chain") # @pytest.mark.only_for_pool_type(0) # class TestFactoryAddPools: From 97f4ddbaf19c74b6c384c04b60e0ee7945ae58ee Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 6 Jul 2023 12:35:49 +0200 Subject: [PATCH 063/337] fix plain tests --- tests/fixtures/pools.py | 12 ++++++------ tests/fixtures/tokens.py | 11 +++++------ tests/test_factory.py | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 5c4feba0..52c94b7a 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -75,36 +75,36 @@ def swap( fee = 1000000 method_id = bytes(b"") oracle = zero_address - asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + # asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other is_rebasing = False metapool_token_type = pool_token_types[0] if metapool_token_type == 0: A = 2000 fee = 1000000 - asset_type = 0 + # asset_type = 0 elif metapool_token_type == 1: A = 1000 fee = 3000000 - asset_type = 1 + # asset_type = 1 elif metapool_token_type == 2: A = 1000 fee = 3000000 - asset_type = 1 + # asset_type = 1 method_id = oracle_method_id oracle = underlying_tokens[0].address elif metapool_token_type == 3: A = 500 fee = 4000000 - asset_type = 1 + # asset_type = 1 is_rebasing = True pool = factory.deploy_metapool( base_pool.address, "test", "test", - pool_tokens[0].address, + underlying_tokens[0].address, A, fee, 866, diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 00d8a293..19601977 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -96,20 +96,19 @@ def base_pool_lp_token(deployer): def underlying_tokens( pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens, base_pool_tokens, base_pool_lp_token ): - pool_tokens = [] metapool_token_type = pool_token_types[0] if metapool_token_type == 0: - pool_tokens.append(plain_tokens[0]) + pool_token = plain_tokens[0] elif metapool_token_type == 1: - pool_tokens = pool_tokens.append(weth) + pool_token = weth elif metapool_token_type == 2: - pool_tokens.append(oracle_tokens[0]) + pool_token = oracle_tokens[0] elif metapool_token_type == 3: - pool_tokens.append(rebase_tokens[0]) + pool_token = rebase_tokens[0] else: raise ValueError("Wrong pool token type") - return pool_tokens + [base_pool_lp_token.address, *[t.address for t in base_pool_tokens]] + return [pool_token] + [base_pool_lp_token, *base_pool_tokens] # <--------------------- Gauge rewards ---------------------> diff --git a/tests/test_factory.py b/tests/test_factory.py index 43608f65..1a869fb4 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -73,15 +73,15 @@ def test_factory(self, factory, swap): def test_get_underlying_coins(self, factory, swap, underlying_tokens, pool_size, zero_address): assert factory.get_underlying_coins(swap) == underlying_tokens + [zero_address] * (MAX_COINS - pool_size) - def test_get_underlying_decimals(self, factory, swap, base_pool_decimals): + def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): assert factory.get_underlying_decimals(swap) == base_pool_decimals + [0] * ( MAX_COINS - len(base_pool_decimals) ) @pytest.mark.parametrize("idx", range(1, 4)) - def test_find_pool_for_coins_underlying(self, factory, swap, underlying_coins, idx): - assert factory.find_pool_for_coins(underlying_coins[0], underlying_coins[idx]) == swap - assert factory.find_pool_for_coins(underlying_coins[idx], underlying_coins[0]) == swap + def test_find_pool_for_coins_underlying(self, factory, swap, underlying_tokens, idx): + assert factory.find_pool_for_coins(underlying_tokens[0], underlying_tokens[idx]) == swap + assert factory.find_pool_for_coins(underlying_tokens[idx], underlying_tokens[0]) == swap def test_get_metapool_rates(self, factory, swap, base_pool): assert factory.get_metapool_rates(swap) == [10**18, base_pool.get_virtual_price()] From 39b94ff2e40ffe8364286b9148c07f4bd76ad7a6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:05:02 +0200 Subject: [PATCH 064/337] wip converting to dynarray impl --- contracts/main/CurveStableSwap2NG.vy | 149 ++++++++++++--------------- 1 file changed, 67 insertions(+), 82 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 62202322..dc014eee 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -123,18 +123,17 @@ MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) -N_COINS: constant(uint256) = 2 -N_COINS_128: constant(int128) = 2 +N_COINS: immutable(uint256) PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(bool[N_COINS]) -PERMISSIONED: public(constant(bool)) = False # Implementation contains permissionless tokens +IS_REBASING: immutable(DynArray[bool, MAX_COINS]) +PERMISSIONED: public(constant(bool)) = False # Implementation does not impose transfer restrictions # to denote that it is a plain pool: BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 factory: public(immutable(Factory)) -coins: public(immutable(address[N_COINS])) -stored_balances: uint256[N_COINS] +coins: public(immutable(DynArray[address, MAX_COINS])) +stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -154,13 +153,13 @@ future_A_time: public(uint256) ADMIN_FEE: constant(uint256) = 5000000000 MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 -admin_balances: public(uint256[N_COINS]) +admin_balances: public(DynArray[uint256, MAX_COINS]) # ----------------------- Oracle Specific vars ------------------------------- -rate_multipliers: uint256[N_COINS] +rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: uint256[N_COINS] +oracles: DynArray[uint256, MAX_COINS] last_prices_packed: uint256 # [last_price, ma_price] ma_exp_time: public(uint256) @@ -200,15 +199,15 @@ CACHED_DOMAIN_SEPARATOR: immutable(bytes32) def __init__( _name: String[32], _symbol: String[10], - _coins: address[MAX_COINS], - _rate_multipliers: uint256[MAX_COINS], _A: uint256, _fee: uint256, - _weth: address, _ma_exp_time: uint256, - _method_ids: bytes4[MAX_COINS], - _oracles: address[MAX_COINS], - _is_rebasing: bool[MAX_COINS] + _weth: address, + _coins: DynArray[address, MAX_COINS], + _rate_multipliers: DynArray[uint256, MAX_COINS], + _method_ids: DynArray[bytes4, MAX_COINS], + _oracles: DynArray[address, MAX_COINS], + _is_rebasing: DynArray[bool, MAX_COINS], ): """ @notice Initialize the pool contract @@ -235,25 +234,29 @@ def __init__( """ WETH20 = _weth - IS_REBASING = [_is_rebasing[0], _is_rebasing[1]] + IS_REBASING = _is_rebasing name = _name symbol = _symbol factory = Factory(msg.sender) - coins = [_coins[0], _coins[1]] + coins = _coins + N_COINS = len(_coins) + __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(N_COINS): + for i in range(MAX_COINS): - if _coins[i] == empty(address): + if i == N_COINS: break # Enforce native token as coin[0] (makes integrators happy) - if _coins[i] == WETH20 and i > 0: - raise "ETH must be at index 0" + if _coins[i] == WETH20: + assert i == 0, "ETH must be at index 0" - self.rate_multipliers[i] = _rate_multipliers[i] + __rate_multipliers[i] = _rate_multipliers[i] self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + rate_multipliers = __rate_multipliers + A: uint256 = _A * A_PRECISION self.initial_A = A self.future_A = A @@ -261,7 +264,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: check if this line is correct + self.last_prices_packed = self.pack_prices(10**18, 10**18) self.ma_last_time = block.timestamp # EIP712 @@ -329,57 +332,58 @@ def _transfer_in( @params use_eth True if the transfer is ETH, False otherwise. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ - coin: address = coins[coin_idx] is_rebasing: bool = True in IS_REBASING + _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - if use_eth and coin == WETH20: # <----------- Pool receives native token. + # ------------------------- Handle Transfers ----------------------------- - assert mvalue == dx # dev: incorrect eth amount - WETH(WETH20).deposit(value=dx) # <--- deposit incoming native token. + if use_eth and coins[coin_idx] == WETH20: - return dx + _dx = mvalue + WETH(WETH20).deposit(value=dx) - if expect_optimistic_transfer: # <---- Pool expects _dx of coin transferred to it by the user. + elif expect_optimistic_transfer: assert not is_rebasing + _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] - assert ERC20(coin).balanceOf(self) - self.stored_balances[coin_idx] == dx, "Pool did not receive tokens for swap" - - return dx - - # ---- For normal ERC20 token handling: with callbacks! - - _dx: uint256 = ERC20(coin).balanceOf(self) - - if callback_sig != empty(bytes32): # <---- ERC20 token transfer is handled by callback. + elif callback_sig != empty(bytes32): raw_call( callbacker, concat( slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coin, dx, dy) + _abi_encode(sender, receiver, coins[coin_idx], dx, dy) ) ) - else: # <--- Pool calls ERC20.transferFrom if msg.sender has given approval. + _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx - assert ERC20(coin).transferFrom( + else: + + assert ERC20(coins[coin_idx]).transferFrom( sender, self, dx, default_return_value=True ) - # Check if the pool received tokens at all. _dx == dx if coins are not rebasing - _dx = ERC20(coin).balanceOf(self) - _dx + _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + + # --------------------------- Check Transfer ----------------------------- + if is_rebasing: - assert _dx > 0 # dev: pool received 0 tokens - return _dx + assert _dx > 0, "Pool did not receive tokens for swap" + else: + assert dx == _dx, "Pool did not receive tokens for swap" + + # ----------------------- Update Stored Balances ------------------------- + + self.stored_balances += _dx - assert dx == _dx return _dx @internal def _transfer_out( - _coin: address, _amount: uint256, use_eth: bool, receiver: address + _coin_idx: int128, _amount: uint256, use_eth: bool, receiver: address ): """ @notice Transfer a single token from the pool to receiver. @@ -391,35 +395,43 @@ def _transfer_out( @params receiver Address to send the tokens to """ - if use_eth and _coin == WETH20: + # ------------------------- Handle Transfers ----------------------------- + + if use_eth and coins[_coin_idx] == WETH20: WETH(WETH20).withdraw(_amount) raw_call(receiver, b"", value=_amount) else: - assert ERC20(_coin).transfer( + assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True ) + # ----------------------- Update Stored Balances ------------------------- + + self.stored_balances[_coin_idx] -= _amount + # -------------------------- AMM Special Methods ----------------------------- @view @internal -def _stored_rates() -> uint256[N_COINS]: +def _stored_rates() -> DynArray[uint256, MAX_COINS]: """ @notice Gets rate multipliers for each coin. @dev If the coin has a rate oracle that has been properly initialised, this method queries that rate by static-calling an external contract. """ + rates: DynArray[uint256, MAX_COINS] = rate_multipliers + oracles: DynArray[uint256, MAX_COINS] = self.oracles - rates: uint256[N_COINS] = self.rate_multipliers - oracles: uint256[N_COINS] = self.oracles + for i in range(MAX_COINS): - for i in range(N_COINS): + if i == N_COINS: + break if oracles[i] == 0: continue @@ -440,44 +452,18 @@ def _stored_rates() -> uint256[N_COINS]: @view @internal -def _balances() -> uint256[N_COINS]: +def _balances() -> DynArray[uint256, MAX_COINS]: """ @notice Calculates the pool's balances _excluding_ the admin's balances. @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ - result: uint256[N_COINS] = empty(uint256[N_COINS]) + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(N_COINS): result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result -@internal -def _increase_balances(balances: uint256[N_COINS]): - """ - @notice Increases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer into the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] += balances[i] - self.stored_balances = stored_balances - - -@internal -def _decrease_balances(balances: uint256[N_COINS]): - """ - @notice Decreases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer out of the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] -= balances[i] - self.stored_balances = stored_balances - - # -------------------------- AMM Main Functions ------------------------------ @@ -581,7 +567,6 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not IS_REBASING[i], "Call disabled if IS_REBASING[i] is True" return self._exchange( msg.sender, 0, From aaba6d1f9dbe67b4805b6d63178e5fd8be0b4b83 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:55:40 +0200 Subject: [PATCH 065/337] fix: set correct is_rebasing array for creating metapool --- contracts/main/CurveStableSwapFactoryNG.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 772a34d8..9d2646a0 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -668,7 +668,7 @@ def deploy_metapool( _ma_exp_time, _method_id, _oracle, - _is_rebasing, + is_rebasing, _base_pool, self.base_pool_data[_base_pool].lp_token, self.base_pool_data[_base_pool].coins, From 1e1fe2da3ef5a52a739147747b870844f864def2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:45:59 +0200 Subject: [PATCH 066/337] convert to dynarray implementation --- contracts/main/CurveStableSwap2NG.vy | 262 +++++++++++++++++---------- 1 file changed, 163 insertions(+), 99 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index dc014eee..71209deb 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -53,7 +53,7 @@ interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def calc_token_amount( - _amounts: uint256[MAX_COINS], + _amounts: DynArray[uint256, MAX_COINS], _is_deposit: bool, _pool: address ) -> uint256: view @@ -79,15 +79,15 @@ event TokenExchange: event AddLiquidity: provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] invariant: uint256 token_supply: uint256 event RemoveLiquidity: provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] token_supply: uint256 event RemoveLiquidityOne: @@ -99,8 +99,8 @@ event RemoveLiquidityOne: event RemoveLiquidityImbalance: provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] invariant: uint256 token_supply: uint256 @@ -119,11 +119,13 @@ event ApplyNewFee: MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory +MAX_COINS_128: constant(int128) = 8 # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) N_COINS: immutable(uint256) +N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 IS_REBASING: immutable(DynArray[bool, MAX_COINS]) PERMISSIONED: public(constant(bool)) = False # Implementation does not impose transfer restrictions @@ -240,12 +242,14 @@ def __init__( symbol = _symbol factory = Factory(msg.sender) coins = _coins - N_COINS = len(_coins) + __n_coins: uint256 = len(_coins) + N_COINS = __n_coins + N_COINS_128 = convert(__n_coins, int128) __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS): + for i in range(MAX_COINS_128): - if i == N_COINS: + if i == N_COINS_128: break # Enforce native token as coin[0] (makes integrators happy) @@ -376,7 +380,7 @@ def _transfer_in( # ----------------------- Update Stored Balances ------------------------- - self.stored_balances += _dx + self.stored_balances[coin_idx] += _dx return _dx @@ -428,9 +432,9 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: rates: DynArray[uint256, MAX_COINS] = rate_multipliers oracles: DynArray[uint256, MAX_COINS] = self.oracles - for i in range(MAX_COINS): + for i in range(MAX_COINS_128): - if i == N_COINS: + if i == N_COINS_128: break if oracles[i] == 0: @@ -458,7 +462,12 @@ def _balances() -> DynArray[uint256, MAX_COINS]: @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(N_COINS): + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] return result @@ -586,7 +595,7 @@ def exchange_received( @external @nonreentrant('lock') def add_liquidity( - _amounts: uint256[N_COINS], + _amounts: DynArray[uint256, MAX_COINS], _min_mint_amount: uint256, _use_eth: bool = False, _receiver: address = msg.sender @@ -599,18 +608,21 @@ def add_liquidity( @return Amount of LP tokens received by depositing """ amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances + new_balances: DynArray[uint256, MAX_COINS] = old_balances # -------------------------- Do Transfers In ----------------------------- - for i in range(N_COINS_128): + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break if _amounts[i] > 0: @@ -631,9 +643,6 @@ def add_liquidity( assert total_supply != 0 # dev: initial deposit requires all coins - # Add incoming balance - self._increase_balances(new_balances) - # ------------------------------------------------------------------------ # Invariant after change @@ -642,14 +651,18 @@ def add_liquidity( # We need to recalculate the invariant accounting for fees # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) mint_amount: uint256 = 0 if total_supply > 0: # Only account for fees if we are not the first to deposit base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 new_balance: uint256 = new_balances[i] @@ -661,7 +674,7 @@ def add_liquidity( self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR new_balances[i] -= fees[i] - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D2: uint256 = self.get_D(xp, amp) mint_amount = total_supply * (D2 - D0) / D0 self.save_p(xp, amp, D2) @@ -710,10 +723,7 @@ def remove_liquidity_one_coin( log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(coins[i], dy[0], _use_eth, _receiver) - - # Decrease coin[i] balance in self.stored_balances - self.stored_balances[i] -= dy[0] + self._transfer_out(i, dy[0], _use_eth, _receiver) log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) @@ -725,7 +735,7 @@ def remove_liquidity_one_coin( @external @nonreentrant('lock') def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], + _amounts: DynArray[uint256, MAX_COINS], _max_burn_amount: uint256, _use_eth: bool = False, _receiver: address = msg.sender @@ -738,24 +748,29 @@ def remove_liquidity_imbalance( @return Actual amount of the LP token burned in the withdrawal """ amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() D0: uint256 = self.get_D_mem(rates, old_balances, amp) + new_balances: DynArray[uint256, MAX_COINS] = old_balances + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): if _amounts[i] != 0: new_balances[i] -= _amounts[i] - self._transfer_out(coins[i], _amounts[i], _use_eth, _receiver) - - # Decrease balances in self.stored_balances - self._decrease_balances(_amounts) + self._transfer_out(i, _amounts[i], _use_eth, _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 new_balance: uint256 = new_balances[i] @@ -789,11 +804,11 @@ def remove_liquidity_imbalance( @nonreentrant('lock') def remove_liquidity( _burn_amount: uint256, - _min_amounts: uint256[N_COINS], + _min_amounts: DynArray[uint256, MAX_COINS], _use_eth: bool = False, _receiver: address = msg.sender, _claim_admin_fees: bool = True, -) -> uint256[N_COINS]: +) -> DynArray[uint256, MAX_COINS]: """ @notice Withdraw coins from the pool @dev Withdrawal amounts are based on current deposit ratios @@ -803,24 +818,25 @@ def remove_liquidity( @return List of amounts of coins that were withdrawn """ total_supply: uint256 = self.totalSupply - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - balances: uint256[N_COINS] = self._balances() + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = self._balances() + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break - for i in range(N_COINS): value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(coins[i], value, _use_eth, _receiver) - - # Decrease balances in self.stored_balances - self._decrease_balances(amounts) + self._transfer_out(i, value, _use_eth, _receiver) total_supply -= _burn_amount self.balanceOf[msg.sender] -= _burn_amount self.totalSupply = total_supply log Transfer(msg.sender, empty(address), _burn_amount) - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply) + log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. if _claim_admin_fees: @@ -858,9 +874,9 @@ def _exchange( assert i != j # dev: coin index out of range assert _dx > 0 # dev: do not exchange 0 coins - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) # --------------------------- Do Transfer in ----------------------------- @@ -878,9 +894,6 @@ def _exchange( expect_optimistic_transfer ) - # Update stored balances - self.stored_balances[i] += dx - # ------------------------------------------------------------------------ x: uint256 = xp[i] + dx * rates[i] / PRECISION @@ -908,10 +921,7 @@ def _exchange( # --------------------------- Do Transfer out ---------------------------- - self._transfer_out(coins[j], dy, use_eth, receiver) - - # Update Stored Balances: - self.stored_balances[j] -= dy + self._transfer_out(j, dy, use_eth, receiver) # ------------------------------------------------------------------------ @@ -923,22 +933,21 @@ def _exchange( @internal def _withdraw_admin_fees(): - receiver: address = factory.get_fee_receiver(self) - amounts: uint256[N_COINS] = self.admin_balances + admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances + fee_receiver: address = factory.get_fee_receiver(self) - for i in range(N_COINS): + assert fee_receiver != empty(address) # dev: fee receiver not set - if amounts[i] > 0: + for i in range(MAX_COINS_128): - assert ERC20(coins[i]).transfer( - receiver, - amounts[i], - default_return_value=True - ) + if i == N_COINS_128: + break + + if admin_balances[i] > 0: - self.admin_balances = empty(uint256[N_COINS]) - # Reduce stored balances: - self._decrease_balances(amounts) + self._transfer_out(i, admin_balances[i], False, fee_receiver) + + self.admin_balances = empty(DynArray[uint256, MAX_COINS]) # --------------------------- AMM Math Functions ----------------------------- @@ -946,7 +955,14 @@ def _withdraw_admin_fees(): @view @internal -def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, _D: uint256) -> uint256: +def get_y( + i: int128, + j: int128, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _D: uint256 +) -> uint256: """ Calculate x[j] if one makes x[i] = x @@ -977,13 +993,18 @@ def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, c: uint256 = D Ann: uint256 = amp * N_COINS - for _i in range(N_COINS_128): + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + if _i == i: _x = x elif _i != j: _x = xp[_i] else: continue + S_ += _x c = c * D / (_x * N_COINS) @@ -1006,7 +1027,7 @@ def get_y(i: int128, j: int128, x: uint256, xp: uint256[N_COINS], _amp: uint256, @pure @internal -def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: """ D invariant calculation in non-overflowing integer operations iteratively @@ -1025,7 +1046,7 @@ def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: D: uint256 = S Ann: uint256 = _amp * N_COINS for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + D_P: uint256 = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) Dprev: uint256 = D D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) # Equality with the precision of 1 @@ -1042,7 +1063,12 @@ def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: @pure @internal -def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: +def get_y_D( + A: uint256, + i: int128, + xp: DynArray[uint256, MAX_COINS], + D: uint256 +) -> uint256: """ Calculate x[i] if one reduces D from being calculated for xp to D @@ -1063,7 +1089,11 @@ def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: c: uint256 = D Ann: uint256 = A * N_COINS - for _i in range(N_COINS_128): + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + if _i != i: _x = xp[_i] else: @@ -1112,17 +1142,31 @@ def _A() -> uint256: @pure @internal -def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): +def _xp_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS] +) -> DynArray[uint256, MAX_COINS]: + + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + result[i] = _rates[i] * _balances[i] / PRECISION + return result @view @internal -def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) +def get_D_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS], + _amp: uint256 +) -> uint256: + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) return self.get_D(xp, _amp) @@ -1133,8 +1177,8 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: # * Get current D # * Solve Eqn against y_i for D - _token_amount amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) D0: uint256 = self.get_D(xp, amp) total_supply: uint256 = self.totalSupply @@ -1142,9 +1186,13 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: new_y: uint256 = self.get_y_D(amp, i, xp, D1) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: uint256[N_COINS] = empty(uint256[N_COINS]) + xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for j in range(MAX_COINS_128): + + if j == N_COINS_128: + break - for j in range(N_COINS_128): dx_expected: uint256 = 0 xp_j: uint256 = xp[j] if j == i: @@ -1178,12 +1226,22 @@ def pack_prices(p1: uint256, p2: uint256) -> uint256: @internal @view -def _get_p(xp: uint256[N_COINS], amp: uint256, D: uint256) -> uint256: +def _get_p( + xp: DynArray[uint256, MAX_COINS], + amp: uint256, + D: uint256 +) -> uint256: # dx_0 / dx_1 only, however can have any number of coins in pool ANN: uint256 = amp * N_COINS - Dr: uint256 = D / (N_COINS**N_COINS) - for i in range(N_COINS): + Dr: uint256 = D / pow_mod256(N_COINS, N_COINS) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + Dr = Dr * D / xp[i] + return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) @@ -1199,7 +1257,7 @@ def save_p_from_price(last_price: uint256): @internal -def save_p(xp: uint256[N_COINS], amp: uint256, D: uint256): +def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): """ Saves current price and its EMA """ @@ -1452,7 +1510,7 @@ def ema_price() -> uint256: @view def get_p() -> uint256: amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem( + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() ) D: uint256 = self.get_D(xp, amp) @@ -1516,7 +1574,7 @@ def get_virtual_price() -> uint256: @return LP token virtual price normalized to 1e18 """ amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) D: uint256 = self.get_D(xp, amp) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio @@ -1525,17 +1583,23 @@ def get_virtual_price() -> uint256: @view @external -def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: +def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool +) -> uint256: """ @notice Calculate addition or reduction in token supply from a deposit or withdrawal @param _amounts Amount of each coin being deposited @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amounts: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - for i in range(MAX_COINS): - if i == N_COINS: + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: break + amounts[i] = _amounts[i] views: address = factory.views_implementation() @@ -1574,7 +1638,7 @@ def balances(i: uint256) -> uint256: @view @external -def get_balances() -> uint256[N_COINS]: +def get_balances() -> DynArray[uint256, MAX_COINS]: return self._balances() From fb910f35d4dc00b1d6844fcff247318830e780ad Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:18:39 +0200 Subject: [PATCH 067/337] dynarray implementation in factory --- contracts/main/CurveStableSwap2NG.vy | 8 +- contracts/main/CurveStableSwapFactoryNG.vy | 92 ++++++++++------------ 2 files changed, 45 insertions(+), 55 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 71209deb..1f400475 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -124,13 +124,15 @@ MAX_COINS_128: constant(int128) = 8 # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) -N_COINS: immutable(uint256) +N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 IS_REBASING: immutable(DynArray[bool, MAX_COINS]) -PERMISSIONED: public(constant(bool)) = False # Implementation does not impose transfer restrictions -# to denote that it is a plain pool: +# Implementation does not impose transfer restrictions: +PERMISSIONED: public(constant(bool)) = False + +# To denote that it is a plain pool: BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 factory: public(immutable(Factory)) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index dc5099b8..cbb07d8e 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -10,16 +10,16 @@ struct PoolArray: base_pool: address implementation: address liquidity_gauge: address - coins: address[MAX_COINS] - decimals: uint256[MAX_COINS] + coins: DynArray[address, MAX_COINS] + decimals: DynArray[uint256, MAX_COINS] n_coins: uint256 asset_type: uint256 struct BasePoolArray: lp_token: address fee_receiver: address - coins: address[MAX_COINS] - is_rebasing: bool[MAX_COINS] + coins: DynArray[address, MAX_COINS] + is_rebasing: DynArray[bool, MAX_COINS] decimals: uint256 n_coins: uint256 asset_type: uint256 @@ -66,7 +66,7 @@ event BasePoolAdded: base_pool: address event PlainPoolDeployed: - coins: address[MAX_COINS] + coins: DynArray[address, MAX_COINS] A: uint256 fee: uint256 deployer: address @@ -85,8 +85,8 @@ event LiquidityGaugeDeployed: WETH20: public(immutable(address)) MAX_COINS: constant(uint256) = 8 +MAX_METAPOOL_COINS: constant(uint256) = 2 ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 -OLD_FACTORY: constant(address) = 0x0959158b6040D32d04c301A72CBFD6b39E21c9AE admin: public(address) future_admin: public(address) @@ -183,7 +183,7 @@ def get_meta_n_coins(_pool: address) -> (uint256, uint256): @view @external -def get_coins(_pool: address) -> address[MAX_COINS]: +def get_coins(_pool: address) -> DynArray[address, MAX_COINS]: """ @notice Get the coins within a pool @param _pool Pool address @@ -194,14 +194,14 @@ def get_coins(_pool: address) -> address[MAX_COINS]: @view @external -def get_underlying_coins(_pool: address) -> address[MAX_COINS]: +def get_underlying_coins(_pool: address) -> DynArray[address, MAX_COINS]: """ @notice Get the underlying coins within a pool @dev Reverts if a pool does not exist or is not a metapool @param _pool Pool address @return List of coin addresses """ - coins: address[MAX_COINS] = empty(address[MAX_COINS]) + coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) base_pool: address = self.pool_data[_pool].base_pool assert base_pool != empty(address) # dev: pool is not metapool coins[0] = self.pool_data[_pool].coins[0] @@ -215,14 +215,14 @@ def get_underlying_coins(_pool: address) -> address[MAX_COINS]: @view @external -def get_decimals(_pool: address) -> uint256[MAX_COINS]: +def get_decimals(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get decimal places for each coin within a pool @param _pool Pool address @return uint256 list of decimals """ if self.pool_data[_pool].base_pool != empty(address): - decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) decimals = self.pool_data[_pool].decimals decimals[1] = 18 return decimals @@ -231,7 +231,7 @@ def get_decimals(_pool: address) -> uint256[MAX_COINS]: @view @external -def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: +def get_underlying_decimals(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get decimal places for each underlying coin within a pool @param _pool Pool address @@ -239,9 +239,9 @@ def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: """ # decimals are tightly packed as a series of uint8 within a little-endian bytes32 # the packed value is stored as uint256 to simplify unpacking via shift and modulo - pool_decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + pool_decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) pool_decimals = self.pool_data[_pool].decimals - decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) decimals[0] = pool_decimals[0] base_pool: address = self.pool_data[_pool].base_pool packed_decimals: uint256 = self.base_pool_data[base_pool].decimals @@ -251,6 +251,7 @@ def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: unpacked: uint256 = (packed_decimals >> 8 * i) % 256 if unpacked == 0: break + decimals[i+1] = unpacked return decimals @@ -258,27 +259,27 @@ def get_underlying_decimals(_pool: address) -> uint256[MAX_COINS]: @view @external -def get_metapool_rates(_pool: address) -> uint256[2]: +def get_metapool_rates(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get rates for coins within a metapool @param _pool Pool address @return Rates for each coin, precision normalized to 10**18 """ - rates: uint256[2] = [10**18, 0] + rates: DynArray[uint256, MAX_COINS] = [10**18, 0] rates[1] = CurvePool(self.pool_data[_pool].base_pool).get_virtual_price() return rates @view @external -def get_balances(_pool: address) -> uint256[MAX_COINS]: +def get_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get balances for each coin within a pool @dev For pools using lending, these are the wrapped coin balances @param _pool Pool address @return uint256 list of balances """ - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) if self.pool_data[_pool].base_pool != empty(address): balances[0] = CurvePool(_pool).balances(0) @@ -287,17 +288,19 @@ def get_balances(_pool: address) -> uint256[MAX_COINS]: n_coins: uint256 = self.pool_data[_pool].n_coins for i in range(MAX_COINS): - if i < n_coins: - balances[i] = CurvePool(_pool).balances(i) - else: - balances[i] = 0 + + if i == n_coins: + break + + balances[i] = CurvePool(_pool).balances(i) + return balances @view @external -def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: +def get_underlying_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get balances for each underlying coin within a metapool @param _pool Metapool address @@ -307,7 +310,7 @@ def get_underlying_balances(_pool: address) -> uint256[MAX_COINS]: base_pool: address = self.pool_data[_pool].base_pool assert base_pool != empty(address) # dev: pool is not a metapool - underlying_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + underlying_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) underlying_balances[0] = CurvePool(_pool).balances(0) base_total_supply: uint256 = ERC20(self.pool_data[_pool].coins[1]).totalSupply() @@ -346,14 +349,14 @@ def get_fees(_pool: address) -> (uint256, uint256): @view @external -def get_admin_balances(_pool: address) -> uint256[MAX_COINS]: +def get_admin_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: """ @notice Get the current admin balances (uncollected fees) for a pool @param _pool Pool address @return List of uint256 admin balances """ n_coins: uint256 = self.pool_data[_pool].n_coins - admin_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + admin_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS): if i == n_coins: break @@ -479,15 +482,15 @@ def get_fee_receiver(_pool: address) -> address: def deploy_plain_pool( _name: String[32], _symbol: String[10], - _coins: address[MAX_COINS], + _coins: DynArray[address, MAX_COINS], _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _method_ids: bytes4[MAX_COINS] = empty(bytes4[MAX_COINS]), - _oracles: address[MAX_COINS] = empty(address[MAX_COINS]), + _method_ids: DynArray[bytes4, MAX_COINS] = empty(DynArray[bytes4, MAX_COINS]), + _oracles: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]), _asset_type: uint256 = 0, _implementation_idx: uint256 = 0, - _is_rebasing: bool[MAX_COINS] = empty(bool[MAX_COINS]) + _is_rebasing: DynArray[bool, MAX_COINS] = empty(DynArray[bool, MAX_COINS]) ) -> address: """ @notice Deploy a new plain pool @@ -521,8 +524,8 @@ def deploy_plain_pool( assert _fee <= 100000000, "Invalid fee" n_coins: uint256 = MAX_COINS - rate_multipliers: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - decimals: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS): @@ -646,8 +649,8 @@ def deploy_metapool( assert decimals < 19, "Max 18 decimals for coins" # combine _coins's _is_rebasing and basepool coins _is_rebasing: - base_pool_is_rebasing: bool[MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing - is_rebasing: bool[MAX_COINS] = empty(bool[MAX_COINS]) + base_pool_is_rebasing: DynArray[bool, MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing + is_rebasing: DynArray[bool, MAX_COINS] = empty(DynArray[bool, MAX_COINS] ) is_rebasing[0] = _is_rebasing for i in range(MAX_COINS): @@ -734,10 +737,10 @@ def add_base_pool( _base_pool: address, _base_lp_token: address, _fee_receiver: address, - _coins: address[MAX_COINS], + _coins: DynArray[address, MAX_COINS], _asset_type: uint256, _n_coins: uint256, - _is_rebasing: bool[MAX_COINS], + _is_rebasing: DynArray[bool, MAX_COINS], ): """ @notice Add a base pool to the registry, which may be used in factory metapools @@ -762,7 +765,7 @@ def add_base_pool( self.base_pool_data[_base_pool].asset_type = _asset_type decimals: uint256 = 0 - coins: address[MAX_COINS] = _coins + coins: DynArray[address, MAX_COINS] = _coins for i in range(MAX_COINS): if i == _n_coins: break @@ -829,20 +832,6 @@ def set_views_implementation(_views_implementation: address): self.views_implementation = _views_implementation -@external -def batch_set_pool_asset_type(_pools: address[32], _asset_types: uint256[32]): - """ - @notice Batch set the asset type for factory pools - @dev Used to modify asset types that were set incorrectly at deployment - """ - assert msg.sender in [self.admin] # dev: admin-only function - - for i in range(32): - if _pools[i] == empty(address): - break - self.pool_data[_pools[i]].asset_type = _asset_types[i] - - @external def commit_transfer_ownership(_addr: address): """ @@ -850,7 +839,6 @@ def commit_transfer_ownership(_addr: address): @param _addr Address of the new owner """ assert msg.sender == self.admin # dev: admin only - self.future_admin = _addr From 7b2c70e7feba95985a02496f0e4e77045460bced Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:21:47 +0200 Subject: [PATCH 068/337] fix: update interfaces --- contracts/main/CurveStableSwapFactoryNG.vy | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index cbb07d8e..e6f02fd2 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -41,19 +41,12 @@ interface CurvePool: def balances(i: uint256) -> uint256: view def admin_balances(i: uint256) -> uint256: view def get_virtual_price() -> uint256: view - def initialize( - _name: String[32], - _symbol: String[10], - _coin: address, - _rate_multiplier: uint256, - _A: uint256, - _fee: uint256, - ): nonpayable - def exchange( # TODO: change this! + def exchange( i: int128, j: int128, dx: uint256, min_dy: uint256, + _use_eth: bool, _receiver: address, ) -> uint256: nonpayable @@ -884,5 +877,5 @@ def convert_metapool_fees() -> bool: amount: uint256 = ERC20(coin).balanceOf(self) receiver: address = self.base_pool_data[base_pool].fee_receiver - CurvePool(msg.sender).exchange(0, 1, amount, 0, receiver) + CurvePool(msg.sender).exchange(0, 1, amount, 0, False, receiver) return True From 1d1315d863cbc86a1008612120ea7d8ccebc8883 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:25:15 +0200 Subject: [PATCH 069/337] dynarray for handler --- .../main/CurveStableSwapFactoryNGHandler.vy | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index 196c7935..1c86e019 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -9,62 +9,55 @@ # ---- interfaces ---- # interface BaseRegistry: def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address: view - def get_admin_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def get_admin_balances(_pool: address) -> DynArray[uint256, MAX_METAREGISTRY_COINS]: view def get_A(_pool: address) -> uint256: view - def get_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def get_balances(_pool: address) -> DynArray[uint256, MAX_METAREGISTRY_COINS]: view def get_base_pool(_pool: address) -> address: view - def get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view + def get_coins(_pool: address) -> DynArray[address, MAX_METAREGISTRY_COINS]: view def get_coin_indices(_pool: address, _from: address, _to: address) -> (int128, int128): view - def get_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def get_decimals(_pool: address) -> DynArray[uint256, MAX_METAREGISTRY_COINS]: view def get_fees(_pool: address) -> uint256[2]: view def get_gauge(_pool: address) -> address: view def get_lp_token(_pool: address) -> address: view def get_meta_n_coins(_pool: address) -> (uint256, uint256): view def get_n_coins(_pool: address) -> uint256: view def get_pool_asset_type(_pool: address) -> uint256: view - def get_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view - def get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view - def get_underlying_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: view + def get_underlying_balances(_pool: address) -> DynArray[uint256, MAX_METAREGISTRY_COINS]: view + def get_underlying_coins(_pool: address) -> DynArray[address, MAX_METAREGISTRY_COINS]: view + def get_underlying_decimals(_pool: address) -> DynArray[uint256, MAX_METAREGISTRY_COINS]: view def is_meta(_pool: address) -> bool: view def pool_count() -> uint256: view def pool_list(pool_id: uint256) -> address: view - interface BasePoolRegistry: def get_base_pool_for_lp_token(_lp_token: address) -> address: view def get_n_coins(_pool: address) -> uint256: view - def get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: view + def get_coins(_pool: address) -> DynArray[address, MAX_METAREGISTRY_COINS]: view def get_lp_token(_pool: address) -> address: view def is_legacy(_pool: address) -> bool: view def base_pool_list(i: uint256) -> address: view - interface CurveLegacyPool: def balances(i: int128) -> uint256: view - interface CurvePool: def admin_balances(i: uint256) -> uint256: view def balances(i: uint256) -> uint256: view def get_virtual_price() -> uint256: view - interface ERC20: def balanceOf(_addr: address) -> uint256: view def decimals() -> uint256: view def name() -> String[64]: view def totalSupply() -> uint256: view - interface GaugeController: def gauge_types(gauge: address) -> int128: view def gauges(i: uint256) -> address: view - interface Gauge: def is_killed() -> bool: view - interface MetaRegistry: def registry_length() -> uint256: view @@ -96,9 +89,11 @@ def _is_meta(_pool: address) -> bool: @internal @view def _get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: - _coins: address[MAX_METAREGISTRY_COINS] = self.base_registry.get_coins(_pool) + _coins: DynArray[address, MAX_METAREGISTRY_COINS] = self.base_registry.get_coins(_pool) _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) for i in range(MAX_METAREGISTRY_COINS): + if i == len(_coins): + break _padded_coins[i] = _coins[i] return _padded_coins @@ -106,9 +101,11 @@ def _get_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: @internal @view def _get_underlying_coins(_pool: address) -> address[MAX_METAREGISTRY_COINS]: - _coins: address[MAX_METAREGISTRY_COINS] = self.base_registry.get_underlying_coins(_pool) + _coins: DynArray[address, MAX_METAREGISTRY_COINS] = self.base_registry.get_underlying_coins(_pool) _padded_coins: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) for i in range(MAX_METAREGISTRY_COINS): + if i == len(_coins): + break _padded_coins[i] = _coins[i] return _padded_coins @@ -147,6 +144,7 @@ def _get_meta_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_CO underlying_balances: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) ul_coins: address[MAX_METAREGISTRY_COINS] = self._get_underlying_coins(_pool) + for i in range(MAX_METAREGISTRY_COINS): if ul_coins[i] == empty(address): @@ -169,9 +167,26 @@ def _get_meta_underlying_balances(_pool: address) -> uint256[MAX_METAREGISTRY_CO @internal @view -def _pad_uint_array(_array: uint256[MAX_METAREGISTRY_COINS]) -> uint256[MAX_METAREGISTRY_COINS]: +def _pad_uint_dynarray( + _array: DynArray[uint256, MAX_METAREGISTRY_COINS] +) -> uint256[MAX_METAREGISTRY_COINS]: _padded_array: uint256[MAX_METAREGISTRY_COINS] = empty(uint256[MAX_METAREGISTRY_COINS]) + array_len: uint256 = len(_array) for i in range(MAX_METAREGISTRY_COINS): + if i == array_len: + break + _padded_array[i] = _array[i] + return _padded_array + + +@internal +@view +def _pad_addr_dynarray(_array: DynArray[address, MAX_METAREGISTRY_COINS]) -> address[MAX_METAREGISTRY_COINS]: + _padded_array: address[MAX_METAREGISTRY_COINS] = empty(address[MAX_METAREGISTRY_COINS]) + array_len: uint256 = len(_array) + for i in range(MAX_METAREGISTRY_COINS): + if i == array_len: + break _padded_array[i] = _array[i] return _padded_array @@ -179,13 +194,13 @@ def _pad_uint_array(_array: uint256[MAX_METAREGISTRY_COINS]) -> uint256[MAX_META @internal @view def _get_balances(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: - return self._pad_uint_array(self.base_registry.get_balances(_pool)) + return self._pad_uint_dynarray(self.base_registry.get_balances(_pool)) @internal @view def _get_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: - return self._pad_uint_array(self.base_registry.get_decimals(_pool)) + return self._pad_uint_dynarray(self.base_registry.get_decimals(_pool)) @internal @@ -495,7 +510,7 @@ def get_underlying_decimals(_pool: address) -> uint256[MAX_METAREGISTRY_COINS]: """ if not self._is_meta(_pool): return self._get_decimals(_pool) - return self.base_registry.get_underlying_decimals(_pool) + return self._pad_uint_dynarray(self.base_registry.get_underlying_decimals(_pool)) @external From 75253a6d12d1d902f74237504df6e62f999d2270 Mon Sep 17 00:00:00 2001 From: Oleg Date: Fri, 7 Jul 2023 11:44:16 +0200 Subject: [PATCH 070/337] add meta tests --- tests/test_factory.py | 74 +++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/tests/test_factory.py b/tests/test_factory.py index 1a869fb4..51dff048 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -7,6 +7,21 @@ class TestFactory: + class TestGeneral: + def test_get_A(self, factory, swap): + assert factory.get_A(swap.address) == swap.A() + + def test_get_fees(self, factory, swap): + assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) + + def test_get_admin_balances(self, factory, swap, pool_size): + balances = [swap.admin_balances(i) for i in range(pool_size)] + assert factory.get_admin_balances(swap.address) == balances + [0] * (MAX_COINS - len(balances)) + + def test_fee_receiver(self, factory, swap, fee_receiver): + assert factory.get_fee_receiver(swap.address) == fee_receiver + + @pytest.mark.only_for_pool_type(0) class TestBasic: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receiving): @@ -62,29 +77,40 @@ def test_get_implementation_address(self, factory, swap, amm_implementation_plai def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is False - def test_fee_receiver(self, factory, swap, fee_receiver): - assert factory.get_fee_receiver(swap.address) == fee_receiver - @pytest.mark.only_for_pool_type(1) class TestMeta: - def test_factory(self, factory, swap): - assert factory.get_meta_n_coins(swap) == [2, 4] - - def test_get_underlying_coins(self, factory, swap, underlying_tokens, pool_size, zero_address): - assert factory.get_underlying_coins(swap) == underlying_tokens + [zero_address] * (MAX_COINS - pool_size) - - def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): - assert factory.get_underlying_decimals(swap) == base_pool_decimals + [0] * ( - MAX_COINS - len(base_pool_decimals) + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + @pytest.mark.only_for_token_types(0, 2, 3) + def test_find_pool_for_coins(self, factory, swap, underlying_tokens, sending, receiving): + assert ( + factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) + == swap.address ) @pytest.mark.parametrize("idx", range(1, 4)) def test_find_pool_for_coins_underlying(self, factory, swap, underlying_tokens, idx): - assert factory.find_pool_for_coins(underlying_tokens[0], underlying_tokens[idx]) == swap - assert factory.find_pool_for_coins(underlying_tokens[idx], underlying_tokens[0]) == swap + assert factory.find_pool_for_coins(underlying_tokens[0], underlying_tokens[idx]) == swap.address + assert factory.find_pool_for_coins(underlying_tokens[idx], underlying_tokens[0]) == swap.address + + def test_get_meta_n_coins(self, factory, swap): + assert factory.get_meta_n_coins(swap.address) == (2, 4) + + def test_get_underlying_coins(self, factory, swap, underlying_tokens, zero_address): + tokens = [underlying_tokens[0]] + underlying_tokens[2:] + assert factory.get_underlying_coins(swap.address) == [t.address for t in tokens] + [zero_address] * ( + MAX_COINS - len(tokens) + ) + + def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): + assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals + [0] * ( + MAX_COINS - len(base_pool_decimals) - 1 + ) def test_get_metapool_rates(self, factory, swap, base_pool): - assert factory.get_metapool_rates(swap) == [10**18, base_pool.get_virtual_price()] + assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] + + def test_get_underlying_balances(self, factory, swap, base_pool): + assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) def test_find_pool_underlying_base_pool_only( @@ -92,19 +118,25 @@ def test_find_pool_underlying_base_pool_only( ): assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(2, 5), 2)) def test_get_coin_indices_underlying(self, factory, swap, sending, receiving, underlying_tokens): i, j, is_underlying = factory.get_coin_indices( swap, underlying_tokens[sending], underlying_tokens[receiving] ) - assert i == sending - assert j == receiving - assert is_underlying is False + assert i == sending - 1 + assert j == receiving - 1 + assert is_underlying is True @pytest.mark.parametrize("idx", range(1, 4)) - def test_get_coin_indices_reverts(self, factory, swap, base_lp_token, underlying_tokens, idx): + def test_get_coin_indices_reverts(self, factory, swap, base_pool_lp_token, underlying_tokens, idx): with boa.reverts(): - factory.get_coin_indices(swap, base_lp_token, underlying_tokens[idx]) + factory.get_coin_indices(swap.address, base_pool_lp_token.address, underlying_tokens[idx]) + + def test_get_implementation_address(self, factory, swap, amm_implementation_meta): + assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address + + def test_is_meta(self, factory, swap): + assert factory.is_meta(swap.address) is True # @pytest.mark.usefixtures("forked_chain") # @pytest.mark.only_for_pool_type(0) From d071f382f9c31f2b211a6b83f8a8fd8f7dc9805f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:58:59 +0200 Subject: [PATCH 071/337] dynarray in views --- contracts/main/CurveStableSwapNGViews.vy | 119 ++++++++++++----------- 1 file changed, 61 insertions(+), 58 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 5d82e98f..7061a3d9 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -7,7 +7,7 @@ integrators """ -interface StableSwap: +interface StableSwapNG: def N_COINS() -> uint256: view def BASE_POOL() -> address: view def BASE_N_COINS() -> uint256: view @@ -18,6 +18,7 @@ interface StableSwap: def A() -> uint256: view def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def totalSupply() -> uint256: view + def calc_token_amount(amounts: DynArray[uint256, MAX_COINS], deposit: bool) -> uint256: view interface StableSwap2: def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view @@ -25,8 +26,7 @@ interface StableSwap2: interface StableSwap3: def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view -interface StableSwap4: - def calc_token_amount(amounts: uint256[4], deposit: bool) -> uint256: view +# TODO: Add up until 7 (a basepool can have maximally 7 if 8 is MAX_COINS. the other 1 is the non base pool token.) A_PRECISION: constant(uint256) = 100 @@ -49,14 +49,14 @@ def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ - N_COINS: uint256 = StableSwap(pool).N_COINS() + N_COINS: uint256 = StableSwapNG(pool).N_COINS() - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) - y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwap(pool).fee()) + y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwapNG(pool).fee()) x: uint256 = self.get_y(j, i, y, xp, 0, 0, N_COINS) return (x - xp[i]) * PRECISION / rates[i] @@ -72,18 +72,18 @@ def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ - N_COINS: uint256 = StableSwap(pool).N_COINS() + N_COINS: uint256 = StableSwapNG(pool).N_COINS() - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) x: uint256 = xp[i] + (dx * rates[i] / PRECISION) y: uint256 = self.get_y(i, j, x, xp, 0, 0, N_COINS) dy: uint256 = xp[j] - y - 1 - fee: uint256 = StableSwap(pool).fee() * dy / FEE_DENOMINATOR + fee: uint256 = StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR return (dy - fee) * PRECISION / rates[j] @@ -116,13 +116,13 @@ def get_dy_underlying( @return Amount of `j` predicted """ - N_COINS: uint256 = StableSwap(pool).N_COINS() + N_COINS: uint256 = StableSwapNG(pool).N_COINS() MAX_COIN: int128 = convert(N_COINS, int128) - 1 - BASE_POOL: address = StableSwap(pool).BASE_POOL() + BASE_POOL: address = StableSwapNG(pool).BASE_POOL() - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) x: uint256 = 0 @@ -143,24 +143,24 @@ def get_dy_underlying( else: if j == 0: # i is from BasePool - base_n_coins: uint256 = StableSwap(pool).BASE_N_COINS() - x = self._meta_calc_token_amounts_deposit( + base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() + x = self._base_calc_token_amounts_deposit( dx, base_i, rates[1], base_n_coins, BASE_POOL ) # Accounting for deposit/withdraw fees approximately - x -= x * StableSwap(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) + x -= x * StableSwapNG(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) # Adding number of pool tokens x += xp[MAX_COIN] else: # If both are from the base pool - return StableSwap(BASE_POOL).get_dy(base_i, base_j, dx) + return StableSwapNG(BASE_POOL).get_dy(base_i, base_j, dx) # This pool is involved only when in-pool assets are used - amp: uint256 = StableSwap(pool).A() * A_PRECISION + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION D: uint256 = self.get_D(xp, amp, N_COINS) y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D, N_COINS) dy: uint256 = xp[meta_j] - y - 1 - dy = (dy - StableSwap(pool).fee() * dy / FEE_DENOMINATOR) + dy = (dy - StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR) # If output is going via the metapool if j == 0: @@ -168,35 +168,38 @@ def get_dy_underlying( else: # j is from BasePool # The fee is already accounted for - dy = StableSwap(BASE_POOL).calc_withdraw_one_coin(dy * PRECISION / rates[1], base_j) + dy = StableSwapNG(BASE_POOL).calc_withdraw_one_coin(dy * PRECISION / rates[1], base_j) return dy @view @external -def calc_token_amount(_amounts: uint256[MAX_COINS], _is_deposit: bool, pool: address) -> uint256: +def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool, + pool: address +) -> uint256: """ @notice Calculate addition or reduction in token supply from a deposit or withdrawal @param _amounts Amount of each coin being deposited @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amp: uint256 = StableSwap(pool).A() * A_PRECISION - N_COINS: uint256 = StableSwap(pool).N_COINS() + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + N_COINS: uint256 = StableSwapNG(pool).N_COINS() - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - old_balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + old_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) # Initial invariant D0: uint256 = self.get_D(xp, amp, N_COINS) - total_supply: uint256 = StableSwap(pool).totalSupply() - new_balances: uint256[MAX_COINS] = old_balances + total_supply: uint256 = StableSwapNG(pool).totalSupply() + new_balances: DynArray[uint256, MAX_COINS] = old_balances for i in range(MAX_COINS): - if i == N_COINS: break @@ -217,10 +220,10 @@ def calc_token_amount(_amounts: uint256[MAX_COINS], _is_deposit: bool, pool: add # to calculate fair user's share D2: uint256 = D1 if total_supply > 0: + # Only account for fees if we are not the first to deposit - base_fee: uint256 = StableSwap(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) for i in range(MAX_COINS): - if i == N_COINS: break @@ -257,22 +260,22 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u # * Get current D # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = StableSwap(pool).A() * A_PRECISION - N_COINS: uint256 = StableSwap(pool).N_COINS() + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + N_COINS: uint256 = StableSwapNG(pool).N_COINS() - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) D0: uint256 = self.get_D(xp, amp, N_COINS) - total_supply: uint256 = StableSwap(pool).totalSupply() + total_supply: uint256 = StableSwapNG(pool).totalSupply() D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = self.get_y_D(amp, i, xp, D1, N_COINS) - base_fee: uint256 = StableSwap(pool).fee() * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for j in range(MAX_COINS): @@ -299,7 +302,7 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u @internal @view -def _meta_calc_token_amounts_deposit( +def _base_calc_token_amounts_deposit( dx: uint256, base_i: int128, meta_vprice: uint256, base_n_coins: uint256, base_pool: address ) -> uint256: @@ -317,9 +320,7 @@ def _meta_calc_token_amounts_deposit( else: - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - return StableSwap4(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + raise "base_n_coins > 3 not supported yet." @internal @@ -348,7 +349,7 @@ def get_y( i: int128, j: int128, x: uint256, - xp: uint256[MAX_COINS], + xp: DynArray[uint256, MAX_COINS], _amp: uint256, _D: uint256, N_COINS: uint256 @@ -402,7 +403,7 @@ def get_y( @pure @internal -def get_D(_xp: uint256[MAX_COINS], _amp: uint256, N_COINS: uint256) -> uint256: +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256, N_COINS: uint256) -> uint256: """ D invariant calculation in non-overflowing integer operations iteratively @@ -444,7 +445,7 @@ def get_D(_xp: uint256[MAX_COINS], _amp: uint256, N_COINS: uint256) -> uint256: def get_y_D( A: uint256, i: int128, - xp: uint256[MAX_COINS], + xp: DynArray[uint256, MAX_COINS], D: uint256, N_COINS: uint256 ) -> uint256: @@ -491,22 +492,24 @@ def get_y_D( @view @internal def _get_rates_balances_xp(pool: address, N_COINS: uint256) -> ( - uint256[MAX_COINS], uint256[MAX_COINS], uint256[MAX_COINS] + DynArray[uint256, MAX_COINS], + DynArray[uint256, MAX_COINS], + DynArray[uint256, MAX_COINS], ): - rates: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - balances: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rate: uint256 = 0 # Get rates and balances: for idx in range(MAX_COINS): if idx == N_COINS: break - rate = StableSwap(pool).stored_rates(idx) - rates[idx] = StableSwap(pool).stored_rates(idx) - balances[idx] = StableSwap(pool).balances(idx) + rate = StableSwapNG(pool).stored_rates(idx) + rates[idx] = StableSwapNG(pool).stored_rates(idx) + balances[idx] = StableSwapNG(pool).balances(idx) - xp: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for idx in range(MAX_COINS): if idx == N_COINS: break From b7dac3eb1b07f2ff8451c00c8cb4e1e7c98d7d4e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:34:20 +0200 Subject: [PATCH 072/337] remove convert_metapool_fees and unify fee claiming process for metapools and plain pools --- contracts/main/CurveStableSwapFactoryNG.vy | 48 +++------------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index e6f02fd2..3ec62a17 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -17,7 +17,6 @@ struct PoolArray: struct BasePoolArray: lp_token: address - fee_receiver: address coins: DynArray[address, MAX_COINS] is_rebasing: DynArray[bool, MAX_COINS] decimals: uint256 @@ -101,8 +100,8 @@ metapool_implementations: public(HashMap[uint256, address]) gauge_implementation: public(address) views_implementation: public(address) -# fee receiver for plain pools -fee_receiver: address +# fee receiver for all pools +fee_receiver: public(address) # mapping of coins -> pools for trading # a mapping key is generated for each pair of addresses via @@ -459,16 +458,6 @@ def get_pool_asset_type(_pool: address) -> uint256: return self.base_pool_data[base_pool].asset_type -@view -@external -def get_fee_receiver(_pool: address) -> address: - base_pool: address = self.pool_data[_pool].base_pool - if base_pool == empty(address): - return self.fee_receiver - else: - return self.base_pool_data[base_pool].fee_receiver - - # <--- Pool Deployers ---> @external @@ -729,7 +718,6 @@ def deploy_gauge(_pool: address) -> address: def add_base_pool( _base_pool: address, _base_lp_token: address, - _fee_receiver: address, _coins: DynArray[address, MAX_COINS], _asset_type: uint256, _n_coins: uint256, @@ -739,7 +727,6 @@ def add_base_pool( @notice Add a base pool to the registry, which may be used in factory metapools @dev Only callable by admin @param _base_pool Pool address to add - @param _fee_receiver Admin fee receiver address for metapools using this base pool @param _asset_type Asset type for pool, as an integer 0 = USD, 1 = ETH, 2 = BTC, 3 = Other @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing """ @@ -753,7 +740,6 @@ def add_base_pool( self.base_pool_count = length + 1 self.base_pool_data[_base_pool].lp_token = _base_lp_token self.base_pool_data[_base_pool].n_coins = _n_coins - self.base_pool_data[_base_pool].fee_receiver = _fee_receiver if _asset_type != 0: self.base_pool_data[_base_pool].asset_type = _asset_type @@ -849,33 +835,11 @@ def accept_transfer_ownership(): @external -def set_fee_receiver(_base_pool: address, _fee_receiver: address): +def set_fee_receiver(_pool: address, _fee_receiver: address): """ - @notice Set fee receiver for base and plain pools - @param _base_pool Address of base pool to set fee receiver for. - For plain pools, leave as `empty(address)`. + @notice Set fee receiver for all pools + @param _pool Address of pool to set fee receiver for. @param _fee_receiver Address that fees are sent to """ assert msg.sender == self.admin # dev: admin only - if _base_pool == empty(address): - self.fee_receiver = _fee_receiver - else: - self.base_pool_data[_base_pool].fee_receiver = _fee_receiver - - -@external -def convert_metapool_fees() -> bool: - """ - @notice Convert the fees of a metapool and transfer to - the metapool's fee receiver - @dev All fees are converted to LP token of base pool - """ - base_pool: address = self.pool_data[msg.sender].base_pool - assert base_pool != empty(address) # dev: sender must be metapool - coin: address = self.pool_data[msg.sender].coins[0] - - amount: uint256 = ERC20(coin).balanceOf(self) - receiver: address = self.base_pool_data[base_pool].fee_receiver - - CurvePool(msg.sender).exchange(0, 1, amount, 0, False, receiver) - return True + self.fee_receiver = _fee_receiver From 70a466e44d29b30461b730a36dcb57116b841771 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 14:17:01 +0200 Subject: [PATCH 073/337] add todos to fix price oracle (currently only works for 2-coin pools) --- contracts/main/CurveStableSwap2NG.vy | 273 +++++++++++++++++++++++---- 1 file changed, 236 insertions(+), 37 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 1f400475..02a4911a 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -58,6 +58,15 @@ interface StableSwapViews: _pool: address ) -> uint256: view +interface StableSwap2: + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable + +interface StableSwap3: + def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable + +interface StableSwap4: + def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable + # --------------------------------- Events ----------------------------------- event Transfer: @@ -120,6 +129,7 @@ event ApplyNewFee: MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 +MAX_METAPOOL_COINS_128: constant(int128) = 2 # ---------------------------- Pool Variables -------------------------------- @@ -133,7 +143,9 @@ IS_REBASING: immutable(DynArray[bool, MAX_COINS]) PERMISSIONED: public(constant(bool)) = False # To denote that it is a plain pool: -BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 +BASE_POOL: public(immutable(address)) +BASE_N_COINS: public(immutable(uint256)) +BASE_COINS: public(immutable(address[MAX_COINS])) factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) @@ -207,6 +219,9 @@ def __init__( _fee: uint256, _ma_exp_time: uint256, _weth: address, + _base_pool: address, + _base_lp_token: address, + _base_coins: DynArray[address, MAX_COINS], # base pool can have maximally (MAX_COINS - 1) coins _coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], _method_ids: DynArray[bytes4, MAX_COINS], @@ -237,12 +252,13 @@ def __init__( a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. """ + BASE_POOL = _base_pool + BASE_COINS = _base_coins + BASE_N_COINS = len(_base_coins) + WETH20 = _weth IS_REBASING = _is_rebasing - name = _name - symbol = _symbol - factory = Factory(msg.sender) coins = _coins __n_coins: uint256 = len(_coins) N_COINS = __n_coins @@ -263,6 +279,12 @@ def __init__( rate_multipliers = __rate_multipliers + # ----------------- Parameters independent of pool type ------------------ + + name = _name + symbol = _symbol + factory = Factory(msg.sender) + A: uint256 = _A * A_PRECISION self.initial_A = A self.future_A = A @@ -672,6 +694,7 @@ def add_liquidity( difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR new_balances[i] -= fees[i] @@ -679,7 +702,7 @@ def add_liquidity( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D2: uint256 = self.get_D(xp, amp) mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) + self.save_p(xp, amp, D2) # TODO: this needs to be for Dynamic N_COINS else: @@ -780,13 +803,14 @@ def remove_liquidity_imbalance( difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance + fees[i] = base_fee * difference / FEE_DENOMINATOR self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR new_balances[i] -= fees[i] D2: uint256 = self.get_D_mem(rates, new_balances, amp) - self.save_p(new_balances, amp, D2) + self.save_p(new_balances, amp, D2) # TODO: this needs to be for Dynamic N_COINS total_supply: uint256 = self.totalSupply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 @@ -858,6 +882,36 @@ def withdraw_admin_fees(): # ------------------------ AMM Internal Functions ---------------------------- +@internal +def __exchange( + dx: uint256, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + rates: DynArray[uint256, MAX_COINS], + i: int128, + j: int128, +) -> uint256: + + amp: uint256 = self._A() + D: uint256 = self.get_D(xp, amp) + y: uint256 = self.get_y(i, j, x, xp, amp, D) + + dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + + self.admin_balances[j] += ( + dy_fee * ADMIN_FEE / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # D is not changed because we did not apply a fee + self.save_p([x, y], amp, D) # TODO: this needs to be for Dynamic N_COINS + + return dy + + @internal def _exchange( sender: address, @@ -919,7 +973,7 @@ def _exchange( xp[i] = x xp[j] = y # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) + self.save_p(xp, amp, D) # TODO: this needs to be for Dynamic N_COINS # --------------------------- Do Transfer out ---------------------------- @@ -932,6 +986,149 @@ def _exchange( return dy +@internal +def _exchange_underlying( + sender: address, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + receiver: address, + callbacker: address, + callback_sig: bytes32, + expect_optimistic_transfer: bool = False +) -> uint256: + + assert BASE_POOL != empty(address) # dev: pool is not a metapool + assert N_COINS == 2 # dev: only N_COINS = 2 supported for metapools + + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + input_coin: address = empty(address) + output_coin: address = empty(address) + + if i == 0: + input_coin = coins[0] + else: + base_i = i - MAX_METAPOOL_COINS_128 # if i == 1, this reverts + meta_i = 1 + input_coin = BASE_COINS[base_i] + if j == 0: + output_coin = coins[0] + else: + base_j = j - MAX_METAPOOL_COINS_128 # if j == 1, this reverts + meta_j = 1 + output_coin = BASE_COINS[base_j] + + # --------------------------- Do Transfer in ----------------------------- + + dx_w_fee: uint256 = 0 + + if expect_optimistic_transfer: + + assert self.is_rebasing[input_coin] # dev: rebasing coins not supported + + # This branch is never reached for rebasing tokens + if input_coin == BASE_COINS[base_i]: + # we expect base_coin's balance to be 0. So swap whatever base_coin's + # balance the pool has: + dx_w_fee = ERC20(input_coin).balanceOf(self) + else: + dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] + assert dx_w_fee == _dx + self.stored_balances[meta_i] += dx_w_fee + + else: + + dx_w_fee = self._transfer_in( + input_coin, + _dx, + _min_dy, + callbacker, + callback_sig, + sender, + receiver, + ) + + # ------------------------------------------------------------------------ + + if i == 0 or j == 0: # meta swap + + if i == 0: + + x = xp[i] + dx_w_fee * rates[i] / PRECISION + + else: + + dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) + x = dx_w_fee * rates[MAX_METAPOOL_COINS_128] / PRECISION + x += xp[MAX_METAPOOL_COINS_128] + + dy = self._exchange_core(dx_w_fee, x, xp, rates, meta_i, meta_j) + + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount + + assert dy >= _min_dy + + # Adjust stored balances: + self.stored_balances[meta_j] -= dy + + else: # base pool swap (user should swap at base pool for better gas) + + dy = ERC20(output_coin).balanceOf(self) + Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy + + # --------------------------- Do Transfer out ---------------------------- + + assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) + + # ------------------------------------------------------------------------ + + log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! + + return dy + + +@internal +def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: + + coin_i: address = coins[MAX_METAPOOL_COINS_128] + x: uint256 = ERC20(coin_i).balanceOf(self) + + if BASE_N_COINS == 2: + + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) + + if BASE_N_COINS == 3: + + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) + + else: + + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) + + return ERC20(coin_i).balanceOf(self) - x + + @internal def _withdraw_admin_fees(): @@ -1217,6 +1414,7 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: # -------------------------- AMM Price Methods ------------------------------- +# TODO: fix this for dynamic N-COINS. The following only works for 2-coin pools. @pure @internal @@ -1244,6 +1442,7 @@ def _get_p( Dr = Dr * D / xp[i] + # TODO: the following does not work for N_COINS > 2; needs i and j to substitute 0 and 1: return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) @@ -1283,6 +1482,36 @@ def _ma_price() -> uint256: return last_ema_price +@view +@external +def last_price() -> uint256: + return self.last_prices_packed & (2**128 - 1) + + +@view +@external +def ema_price() -> uint256: + return (self.last_prices_packed >> 128) + + +@external +@view +def get_p() -> uint256: + amp: uint256 = self._A() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) + D: uint256 = self.get_D(xp, amp) + return self._get_p(xp, amp, D) + + +@external +@view +@nonreentrant('lock') +def price_oracle() -> uint256: + return self._ma_price() + + # ----------------------------- Math Utils ----------------------------------- @@ -1496,36 +1725,6 @@ def DOMAIN_SEPARATOR() -> bytes32: # ------------------------- AMM View Functions ------------------------------- -@view -@external -def last_price() -> uint256: - return self.last_prices_packed & (2**128 - 1) - - -@view -@external -def ema_price() -> uint256: - return (self.last_prices_packed >> 128) - - -@external -@view -def get_p() -> uint256: - amp: uint256 = self._A() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem( - self._stored_rates(), self._balances() - ) - D: uint256 = self.get_D(xp, amp) - return self._get_p(xp, amp, D) - - -@external -@view -@nonreentrant('lock') -def price_oracle() -> uint256: - return self._ma_price() - - @view @external def get_dx(i: int128, j: int128, dy: uint256) -> uint256: From bf062c9599e3976804aefdf084f73a23cfe84000 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 14:20:00 +0200 Subject: [PATCH 074/337] refactor _exchange to reduce redunddant code (which will come in exchange underlying) --- contracts/main/CurveStableSwap2NG.vy | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 02a4911a..9523a471 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -950,31 +950,12 @@ def _exchange( expect_optimistic_transfer ) - # ------------------------------------------------------------------------ + # ------------------------------- Exchange ------------------------------- x: uint256 = xp[i] + dx * rates[i] / PRECISION - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(i, j, x, xp, amp, D) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] + dy: uint256 = self.__exchange(dx, x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" - self.admin_balances[j] += ( - dy_fee * ADMIN_FEE / FEE_DENOMINATOR - ) * PRECISION / rates[j] - - # xp is not used anymore, so we reuse it for price calc - xp[i] = x - xp[j] = y - # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) # TODO: this needs to be for Dynamic N_COINS - # --------------------------- Do Transfer out ---------------------------- self._transfer_out(j, dy, use_eth, receiver) From 245852dc758b8ab18b8eaadd79adf9c3222000c7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 14:50:53 +0200 Subject: [PATCH 075/337] fix: do not set empty() for clearing DynArray storage --- contracts/main/CurveStableSwap2NG.vy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 9523a471..5a773811 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -1126,8 +1126,9 @@ def _withdraw_admin_fees(): if admin_balances[i] > 0: self._transfer_out(i, admin_balances[i], False, fee_receiver) + admin_balances[i] = 0 - self.admin_balances = empty(DynArray[uint256, MAX_COINS]) + self.admin_balances = admin_balances # --------------------------- AMM Math Functions ----------------------------- From 6a4d08f989a7a1d192a38efa93af9bd5f00ea8ad Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:41:37 +0200 Subject: [PATCH 076/337] add metapool logic --- contracts/main/CurveStableSwap2NG.vy | 124 +++++++++++++++++---------- 1 file changed, 79 insertions(+), 45 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 5a773811..f386b6e8 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -38,7 +38,7 @@ implements: ERC20 # ------------------------------- Interfaces --------------------------------- interface Factory: - def get_fee_receiver(_pool: address) -> address: view + def get_fee_receiver() -> address: view def admin() -> address: view def views_implementation() -> address: view @@ -67,6 +67,10 @@ interface StableSwap3: interface StableSwap4: def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable +interface StableSwap: + def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable + def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable + # --------------------------------- Events ----------------------------------- event Transfer: @@ -86,6 +90,13 @@ event TokenExchange: bought_id: int128 tokens_bought: uint256 +event TokenExchangeUnderlying: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + event AddLiquidity: provider: indexed(address) token_amounts: DynArray[uint256, MAX_COINS] @@ -137,7 +148,6 @@ WETH20: immutable(address) N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -IS_REBASING: immutable(DynArray[bool, MAX_COINS]) # Implementation does not impose transfer restrictions: PERMISSIONED: public(constant(bool)) = False @@ -145,12 +155,14 @@ PERMISSIONED: public(constant(bool)) = False # To denote that it is a plain pool: BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) -BASE_COINS: public(immutable(address[MAX_COINS])) +BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 +is_rebasing: HashMap[address, bool] + FEE_DENOMINATOR: constant(uint256) = 10 ** 10 # ---------------------- Pool Amplification Parameters ----------------------- @@ -220,9 +232,8 @@ def __init__( _ma_exp_time: uint256, _weth: address, _base_pool: address, - _base_lp_token: address, - _base_coins: DynArray[address, MAX_COINS], # base pool can have maximally (MAX_COINS - 1) coins _coins: DynArray[address, MAX_COINS], + _base_coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], _method_ids: DynArray[bytes4, MAX_COINS], _oracles: DynArray[address, MAX_COINS], @@ -252,32 +263,36 @@ def __init__( a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. """ + WETH20 = _weth BASE_POOL = _base_pool BASE_COINS = _base_coins - BASE_N_COINS = len(_base_coins) - - WETH20 = _weth - IS_REBASING = _is_rebasing - coins = _coins + __n_coins: uint256 = len(_coins) - N_COINS = __n_coins - N_COINS_128 = convert(__n_coins, int128) - __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + __base_n_coins: uint256 = len(_base_coins) - for i in range(MAX_COINS_128): + BASE_N_COINS = __base_n_coins - if i == N_COINS_128: - break + if BASE_POOL != empty(address): - # Enforce native token as coin[0] (makes integrators happy) - if _coins[i] == WETH20: - assert i == 0, "ETH must be at index 0" + assert __n_coins == 2 # dev: metapools can only have 2 coins - __rate_multipliers[i] = _rate_multipliers[i] - self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + self.is_rebasing[_coins[0]] = _is_rebasing[0] + for i in range(MAX_COINS): + if i == __base_n_coins: + break + self.is_rebasing[_base_coins[i]] = _is_rebasing[i+1] - rate_multipliers = __rate_multipliers + self.last_prices_packed = self.pack_prices(10**18, 10**18) # <--- TODO: check this! + + else: + + __n_coins = len(_coins) + self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: fix this! + + + N_COINS = __n_coins + N_COINS_128 = convert(__n_coins, int128) # ----------------- Parameters independent of pool type ------------------ @@ -292,10 +307,26 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) self.ma_last_time = block.timestamp - # EIP712 + # Rate Multipliers ----------------- + __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + # Enforce native token as coin[0] (makes integrators happy) + if _coins[i] == WETH20: + assert i == 0, "ETH must be at index 0" + + __rate_multipliers[i] = _rate_multipliers[i] + self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) + self.admin_balances[i] = 0 + + rate_multipliers = __rate_multipliers + + # EIP712 related params ----------------- NAME_HASH = keccak256(name) salt = block.prevhash CACHED_CHAIN_ID = chain.id @@ -310,7 +341,8 @@ def __init__( ) ) - # fire a transfer event so block explorers identify the contract as an ERC20 + # ------------------------ Fire a transfer event ------------------------- + log Transfer(empty(address), self, 0) @@ -360,8 +392,8 @@ def _transfer_in( @params use_eth True if the transfer is ETH, False otherwise. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ - is_rebasing: bool = True in IS_REBASING _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) + incoming_coin_is_rebasing: bool = self.is_rebasing[coins[coin_idx]] # ------------------------- Handle Transfers ----------------------------- @@ -372,7 +404,7 @@ def _transfer_in( elif expect_optimistic_transfer: - assert not is_rebasing + assert not incoming_coin_is_rebasing, "exchange_received not allowed if incoming token is rebasing" _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -397,8 +429,8 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if is_rebasing: - assert _dx > 0, "Pool did not receive tokens for swap" + if incoming_coin_is_rebasing: + assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! else: assert dx == _dx, "Pool did not receive tokens for swap" @@ -981,7 +1013,6 @@ def _exchange_underlying( ) -> uint256: assert BASE_POOL != empty(address) # dev: pool is not a metapool - assert N_COINS == 2 # dev: only N_COINS = 2 supported for metapools rates: DynArray[uint256, MAX_COINS] = self._stored_rates() old_balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -1027,17 +1058,21 @@ def _exchange_underlying( assert dx_w_fee == _dx self.stored_balances[meta_i] += dx_w_fee + elif callback_sig != empty(bytes32): + + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, input_coin, _dx, _min_dy) + ) + ) + else: - dx_w_fee = self._transfer_in( - input_coin, - _dx, - _min_dy, - callbacker, - callback_sig, - sender, - receiver, - ) + assert ERC20(input_coin).transferFrom(sender, self, _dx, default_return_value=True) + + dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx # ------------------------------------------------------------------------ @@ -1053,12 +1088,12 @@ def _exchange_underlying( x = dx_w_fee * rates[MAX_METAPOOL_COINS_128] / PRECISION x += xp[MAX_METAPOOL_COINS_128] - dy = self._exchange_core(dx_w_fee, x, xp, rates, meta_i, meta_j) + dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) # Withdraw from the base pool if needed if j > 0: out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) dy = ERC20(output_coin).balanceOf(self) - out_amount assert dy >= _min_dy @@ -1069,7 +1104,7 @@ def _exchange_underlying( else: # base pool swap (user should swap at base pool for better gas) dy = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) dy = ERC20(output_coin).balanceOf(self) - dy # --------------------------- Do Transfer out ---------------------------- @@ -1113,11 +1148,10 @@ def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: @internal def _withdraw_admin_fees(): - admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances - fee_receiver: address = factory.get_fee_receiver(self) - + fee_receiver: address = factory.get_fee_receiver() assert fee_receiver != empty(address) # dev: fee receiver not set + admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(MAX_COINS_128): if i == N_COINS_128: From 4f77c44b42520ed7edd969a388fd49412bc1cef1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:46:06 +0200 Subject: [PATCH 077/337] add price oracle related methods to fix --- contracts/main/CurveStableSwap2NG.vy | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index f386b6e8..e6aafacb 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -270,9 +270,10 @@ def __init__( __n_coins: uint256 = len(_coins) __base_n_coins: uint256 = len(_base_coins) - BASE_N_COINS = __base_n_coins + # ----------------- Parameters dependent of pool type -------------------- + if BASE_POOL != empty(address): assert __n_coins == 2 # dev: metapools can only have 2 coins @@ -1434,7 +1435,7 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: @pure @internal -def pack_prices(p1: uint256, p2: uint256) -> uint256: +def pack_prices(p1: uint256, p2: uint256) -> uint256: # <---- TODO: how to pack 8 numbers? assert p1 < 2**128 assert p2 < 2**128 return p1 | (p2 << 128) @@ -1463,7 +1464,7 @@ def _get_p( @internal -def save_p_from_price(last_price: uint256): +def save_p_from_price(last_price: uint256): # <---- TODO: this should accept last_prices with length MAX_COINS-1 """ Saves current price and its EMA """ @@ -1483,7 +1484,7 @@ def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): @internal @view -def _ma_price() -> uint256: +def _ma_price() -> uint256: # <---- TODO: this one needs an index! ma_last_time: uint256 = self.ma_last_time pp: uint256 = self.last_prices_packed @@ -1500,19 +1501,19 @@ def _ma_price() -> uint256: @view @external -def last_price() -> uint256: +def last_price() -> uint256: # <---- TODO: this one needs an index! return self.last_prices_packed & (2**128 - 1) @view @external -def ema_price() -> uint256: +def ema_price() -> uint256: # <---- TODO: this one needs an index! return (self.last_prices_packed >> 128) @external @view -def get_p() -> uint256: +def get_p() -> uint256: # <---- TODO: this one needs an index! amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() @@ -1524,8 +1525,8 @@ def get_p() -> uint256: @external @view @nonreentrant('lock') -def price_oracle() -> uint256: - return self._ma_price() +def price_oracle() -> uint256: # <---- TODO: this one needs an index! + return self._ma_price() # <---- TODO: this one needs an index! # ----------------------------- Math Utils ----------------------------------- From 2a081c7322d3841104cebc4b253351182f4b904f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 18:32:25 +0200 Subject: [PATCH 078/337] fix factory deployment methods --- contracts/main/CurveStableSwap2NG.vy | 33 +++++--- contracts/main/CurveStableSwapFactoryNG.vy | 98 ++++++++++------------ 2 files changed, 68 insertions(+), 63 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index e6aafacb..c6e5cb55 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -70,6 +70,7 @@ interface StableSwap4: interface StableSwap: def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable + def get_virtual_price() -> uint256: view # --------------------------------- Events ----------------------------------- @@ -274,26 +275,38 @@ def __init__( # ----------------- Parameters dependent of pool type -------------------- + __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + if BASE_POOL != empty(address): assert __n_coins == 2 # dev: metapools can only have 2 coins self.is_rebasing[_coins[0]] = _is_rebasing[0] + # self.is_rebasing[_coins[1]] = False is ignored, + # because _coins[1] is base_lp_token and that does not rebase. + for i in range(MAX_COINS): if i == __base_n_coins: break self.is_rebasing[_base_coins[i]] = _is_rebasing[i+1] + # Approval needed for add_liquidity operation on base pool in _exchange_underlying + ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) + self.last_prices_packed = self.pack_prices(10**18, 10**18) # <--- TODO: check this! + # Only need rate_multiplier for coin paired against basepool's lp token: + __rate_multipliers = [_rate_multipliers[0]] + else: __n_coins = len(_coins) - self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: fix this! - + self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: fix this since there are more than 2 prices! + __rate_multipliers = _rate_multipliers N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) + rate_multipliers = __rate_multipliers # ----------------- Parameters independent of pool type ------------------ @@ -310,22 +323,17 @@ def __init__( self.ma_exp_time = _ma_exp_time self.ma_last_time = block.timestamp - # Rate Multipliers ----------------- - __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS_128): if i == N_COINS_128: break - # Enforce native token as coin[0] (makes integrators happy) + # Enforce native token as coin[0] if _coins[i] == WETH20: assert i == 0, "ETH must be at index 0" - __rate_multipliers[i] = _rate_multipliers[i] self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) - self.admin_balances[i] = 0 - - rate_multipliers = __rate_multipliers + self.admin_balances[i] = 0 # <--- this initialises storage for admin balances # TODO: check if this is needed? # EIP712 related params ----------------- NAME_HASH = keccak256(name) @@ -486,7 +494,12 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: this method queries that rate by static-calling an external contract. """ - rates: DynArray[uint256, MAX_COINS] = rate_multipliers + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + if BASE_POOL != empty(address): + rates = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] + else: + rates = rate_multipliers + oracles: DynArray[uint256, MAX_COINS] = self.oracles for i in range(MAX_COINS_128): diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 3ec62a17..261ad772 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -95,8 +95,7 @@ base_pool_data: public(HashMap[address, BasePoolArray]) base_pool_assets: public(HashMap[address, bool]) # index -> implementation address -plain_implementations: public(HashMap[uint256, address]) -metapool_implementations: public(HashMap[uint256, address]) +pool_implementations: public(HashMap[uint256, address]) gauge_implementation: public(address) views_implementation: public(address) @@ -505,8 +504,8 @@ def deploy_plain_pool( """ assert _fee <= 100000000, "Invalid fee" - n_coins: uint256 = MAX_COINS - rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + n_coins: uint256 = 0 + _rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS): @@ -521,7 +520,7 @@ def deploy_plain_pool( decimals[i] = ERC20(coin).decimals() assert decimals[i] < 19, "Max 18 decimals for coins" - rate_multipliers[i] = 10 ** (36 - decimals[i]) + _rate_multipliers[i] = 10 ** (36 - decimals[i]) for x in range(i, i + MAX_COINS): if x+1 == MAX_COINS: @@ -530,21 +529,24 @@ def deploy_plain_pool( break assert coin != _coins[x+1], "Duplicate coins" - implementation: address = self.plain_implementations[_implementation_idx] + implementation: address = self.pool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" + pool: address = create_from_blueprint( implementation, - _name, - _symbol, - _coins, - rate_multipliers, - _A, - _fee, - WETH20, - _ma_exp_time, - _method_ids, - _oracles, - _is_rebasing, + _name, # _name: String[32] + _symbol, # _symbol: String[10] + _A, # _A: uint256 + _fee, # _fee: uint256 + _ma_exp_time, # _ma_exp_time: uint256 + WETH20, # _weth: address + empty(address), # _base_pool: address + _coins, # _coins: DynArray[address, MAX_COINS] + empty(DynArray[address, MAX_COINS]), # base_coins: DynArray[address, MAX_COINS] + _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] + _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] + _oracles, # _oracles: DynArray[address, MAX_COINS] + _is_rebasing, # _is_rebasing: DynArray[bool, MAX_COINS] code_offset=3 ) @@ -623,7 +625,7 @@ def deploy_metapool( assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" - implementation: address = self.metapool_implementations[_implementation_idx] + implementation: address = self.pool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" # things break if a token has >18 decimals @@ -634,28 +636,34 @@ def deploy_metapool( base_pool_is_rebasing: DynArray[bool, MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing is_rebasing: DynArray[bool, MAX_COINS] = empty(DynArray[bool, MAX_COINS] ) is_rebasing[0] = _is_rebasing - for i in range(MAX_COINS): + for i in range(1, MAX_COINS): - if i+1 == MAX_COINS: + if i == MAX_COINS: break - is_rebasing[i+1] = base_pool_is_rebasing[i] + # is_rebasing[1] is for base_pool's coin[0] + is_rebasing[i] = base_pool_is_rebasing[i - 1] + + _coins: DynArray[address, MAX_COINS] = [_coin, self.base_pool_data[_base_pool].lp_token] + _rate_multipliers: DynArray[uint256, MAX_COINS] = [10 ** (36 - decimals)] + _method_ids: DynArray[bytes4, MAX_COINS] = [_method_id] + _oracles: DynArray[address, MAX_COINS] = [_oracle] pool: address = create_from_blueprint( implementation, - _name, - _symbol, - _coin, - 10 ** (36 - decimals), # rate multiplier for _coin - _A, - _fee, - _ma_exp_time, - _method_id, - _oracle, - _is_rebasing, - _base_pool, - self.base_pool_data[_base_pool].lp_token, - self.base_pool_data[_base_pool].coins, + _name, # _name: String[32] + _symbol, # _symbol: String[10] + _A, # _A: uint256 + _fee, # _fee: uint256 + _ma_exp_time, # _ma_exp_time: uint256 + WETH20, # _weth: address + _base_pool, # _base_pool: address + _coins, # _coins: DynArray[address, MAX_COINS] + self.base_pool_data[_base_pool].coins, # base_coins: DynArray[address, MAX_COINS] + _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] + _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] + _oracles, # _oracles: DynArray[address, MAX_COINS] + _is_rebasing, # _is_rebasing: DynArray[bool, MAX_COINS] code_offset=3 ) @@ -759,34 +767,18 @@ def add_base_pool( @external -def set_metapool_implementations( - _implementation_index: uint256, - _implementation: address, -): - """ - @notice Set implementation contracts for a metapool - @dev Only callable by admin - @param _implementation_index Implementation index where implementation is stored - @param _implementation Implementation address to use when deploying metapools - """ - assert msg.sender == self.admin # dev: admin-only function - self.metapool_implementations[_implementation_index] = _implementation - - - -@external -def set_plain_implementations( +def set_pool_implementations( _implementation_index: uint256, _implementation: address, ): """ - @notice Set implementation contracts for plain pools + @notice Set implementation contracts for pools @dev Only callable by admin @param _implementation_index Implementation index where implementation is stored @param _implementation Implementation address to use when deploying plain pools """ assert msg.sender == self.admin # dev: admin-only function - self.plain_implementations[_implementation_index] = _implementation + self.pool_implementations[_implementation_index] = _implementation @external From fd4f1371054164c022af01c2f4a7c6b7c8e41b26 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 10 Jul 2023 18:35:33 +0200 Subject: [PATCH 079/337] fix test? --- tests/fixtures/factory.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 881f7565..8db98d9e 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -24,17 +24,6 @@ def amm_implementation_plain(deployer, amm_interface_plain): return amm_interface_plain.deploy_as_blueprint() -@pytest.fixture(scope="module") -def amm_interface_meta(): - return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") - - -@pytest.fixture(scope="module") -def amm_implementation_meta(deployer, amm_interface_meta): - with boa.env.prank(deployer): - return amm_interface_meta.deploy_as_blueprint() - - @pytest.fixture(scope="module") def views_implementation(deployer): with boa.env.prank(deployer): @@ -60,37 +49,28 @@ def factory( # <--------------------- Functions ---------------------> @pytest.fixture(scope="module") -def set_plain_implementations(owner, factory, amm_implementation_plain): +def set_pool_implementations(owner, factory, amm_implementation_plain): with boa.env.prank(owner): - factory.set_plain_implementations(0, amm_implementation_plain.address) - - -@pytest.fixture(scope="module") -def set_meta_implementations(owner, factory, amm_implementation_meta): - with boa.env.prank(owner): - factory.set_metapool_implementations(0, amm_implementation_meta.address) + factory.set_pool_implementations(0, amm_implementation_plain.address) @pytest.fixture(scope="module") def add_base_pool( owner, - fee_receiver, factory, base_pool, base_pool_lp_token, base_pool_tokens, - zero_address, ): + with boa.env.prank(owner): factory.add_base_pool( base_pool.address, base_pool_lp_token.address, - fee_receiver, [t.address for t in base_pool_tokens], 0, len(base_pool_tokens), [False] * len(base_pool_tokens), - [zero_address] * len((base_pool_tokens)), ) From b4efdcdc94e0caea34e016a56dd2f6282613b91c Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 11 Jul 2023 00:33:01 +0200 Subject: [PATCH 080/337] fix deploy for new implementation --- contracts/main/CurveStableSwap2NG.vy | 88 +++++++++++----------- contracts/main/CurveStableSwapFactoryNG.vy | 10 ++- tests/fixtures/factory.py | 10 +-- tests/fixtures/pools.py | 11 +-- tests/test_factory.py | 39 ++++------ 5 files changed, 75 insertions(+), 83 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index c6e5cb55..f19550ba 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -332,8 +332,8 @@ def __init__( if _coins[i] == WETH20: assert i == 0, "ETH must be at index 0" - self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) - self.admin_balances[i] = 0 # <--- this initialises storage for admin balances # TODO: check if this is needed? + self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? # EIP712 related params ----------------- NAME_HASH = keccak256(name) @@ -1890,60 +1890,60 @@ def stored_rates(i: uint256) -> uint256: # --------------------------- AMM Admin Functions ---------------------------- -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == factory.admin() # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time +# @external +# def ramp_A(_future_A: uint256, _future_time: uint256): +# assert msg.sender == factory.admin() # dev: only owner +# assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME +# assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION +# _initial_A: uint256 = self._A() +# _future_A_p: uint256 = _future_A * A_PRECISION - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE +# assert _future_A > 0 and _future_A < MAX_A +# if _future_A_p < _initial_A: +# assert _future_A_p * MAX_A_CHANGE >= _initial_A +# else: +# assert _future_A_p <= _initial_A * MAX_A_CHANGE - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time +# self.initial_A = _initial_A +# self.future_A = _future_A_p +# self.initial_A_time = block.timestamp +# self.future_A_time = _future_time - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) +# log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) -@external -def stop_ramp_A(): - assert msg.sender == factory.admin() # dev: only owner +# @external +# def stop_ramp_A(): +# assert msg.sender == factory.admin() # dev: only owner - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A +# current_A: uint256 = self._A() +# self.initial_A = current_A +# self.future_A = current_A +# self.initial_A_time = block.timestamp +# self.future_A_time = block.timestamp +# # now (block.timestamp < t1) is always False, so we return saved A - log StopRampA(current_A, block.timestamp) +# log StopRampA(current_A, block.timestamp) -@external -def apply_new_fee(_new_fee: uint256): +# @external +# def apply_new_fee(_new_fee: uint256): - assert msg.sender == factory.admin() - assert _new_fee <= MAX_FEE - self.fee = _new_fee +# assert msg.sender == factory.admin() +# assert _new_fee <= MAX_FEE +# self.fee = _new_fee - log ApplyNewFee(_new_fee) +# log ApplyNewFee(_new_fee) -@external -def set_ma_exp_time(_ma_exp_time: uint256): - """ - @notice Set the moving average window of the price oracle. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) - """ - assert msg.sender == factory.admin() # dev: only owner - assert _ma_exp_time != 0 +# @external +# def set_ma_exp_time(_ma_exp_time: uint256): +# """ +# @notice Set the moving average window of the price oracle. +# @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) +# """ +# assert msg.sender == factory.admin() # dev: only owner +# assert _ma_exp_time != 0 - self.ma_exp_time = _ma_exp_time +# self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 54f3e77c..8715724f 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -503,8 +503,12 @@ def deploy_plain_pool( @return Address of the deployed pool """ assert _fee <= 100000000, "Invalid fee" + assert len(_coins) == len(_method_ids), "All coin arrays should be same length" + assert len(_coins) == len(_oracles), "All coin arrays should be same length" + assert len(_coins) == len(_asset_types), "All coin arrays should be same length" + assert len(_coins) == len(_is_rebasing), "All coin arrays should be same length" - n_coins: uint256 = 0 + n_coins: uint256 = len(_coins) _rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -514,10 +518,10 @@ def deploy_plain_pool( coin: address = _coins[i] - decimals[i] = ERC20(coin).decimals() + decimals.append(ERC20(coin).decimals()) assert decimals[i] < 19, "Max 18 decimals for coins" - _rate_multipliers[i] = 10 ** (36 - decimals[i]) + _rate_multipliers.append(10 ** (36 - decimals[i])) for j in range(i, i + MAX_COINS): if (j + 1) == n_coins: diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 06555928..7cc5f8f8 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -14,14 +14,14 @@ def gauge_implementation(deployer, gauge_interface): @pytest.fixture(scope="module") -def amm_interface_plain(): +def amm_interface(): return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") @pytest.fixture(scope="module") -def amm_implementation_plain(deployer, amm_interface_plain): +def amm_implementation(deployer, amm_interface): with boa.env.prank(deployer): - return amm_interface_plain.deploy_as_blueprint() + return amm_interface.deploy_as_blueprint() @pytest.fixture(scope="module") @@ -49,9 +49,9 @@ def factory( # <--------------------- Functions ---------------------> @pytest.fixture(scope="module") -def set_pool_implementations(owner, factory, amm_implementation_plain): +def set_pool_implementations(owner, factory, amm_implementation): with boa.env.prank(owner): - factory.set_pool_implementations(0, amm_implementation_plain.address) + factory.set_pool_implementations(0, amm_implementation.address) @pytest.fixture(scope="module") diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index ac3a90d2..510a7aad 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -10,6 +10,7 @@ def swap( deployer, factory, weth, + pool_size, pool_type, pool_token_types, pool_tokens, @@ -17,15 +18,15 @@ def swap( ): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") if pool_type == 0: - amm_interface_plain = request.getfixturevalue("amm_interface_plain") - _ = request.getfixturevalue("set_plain_implementations") + amm_interface_plain = request.getfixturevalue("amm_interface") + _ = request.getfixturevalue("set_pool_implementations") A = 2000 fee = 1000000 - method_ids = [bytes(b"")] * 8 - oracles = [zero_address] * 8 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size asset_type = [] - is_rebasing = [False] * 8 + is_rebasing = [False] * pool_size for i, t in enumerate(pool_token_types): if t == 0: diff --git a/tests/test_factory.py b/tests/test_factory.py index 51dff048..87ed79b7 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -16,10 +16,10 @@ def test_get_fees(self, factory, swap): def test_get_admin_balances(self, factory, swap, pool_size): balances = [swap.admin_balances(i) for i in range(pool_size)] - assert factory.get_admin_balances(swap.address) == balances + [0] * (MAX_COINS - len(balances)) + assert factory.get_admin_balances(swap.address) == balances - def test_fee_receiver(self, factory, swap, fee_receiver): - assert factory.get_fee_receiver(swap.address) == fee_receiver + def test_fee_receiver(self, factory, fee_receiver): + assert factory.fee_receiver() == fee_receiver @pytest.mark.only_for_pool_type(0) class TestBasic: @@ -30,21 +30,17 @@ def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receivin == swap.address ) - def test_get_n_coins(self, factory, swap, pool_tokens, pool_size, zero_address): + def test_get_n_coins(self, factory, swap, pool_tokens, pool_size): assert factory.get_n_coins(swap.address) == 2 - def test_get_coins(self, factory, swap, pool_tokens, pool_size, zero_address): - assert factory.get_coins(swap.address) == [pt.address for pt in pool_tokens] + [zero_address] * ( - MAX_COINS - pool_size - ) + def test_get_coins(self, factory, swap, pool_tokens, pool_size): + assert factory.get_coins(swap.address) == [pt.address for pt in pool_tokens] def test_get_decimals(self, factory, swap, decimals): - assert factory.get_decimals(swap.address) == decimals + [0] * (MAX_COINS - len(decimals)) + assert factory.get_decimals(swap.address) == decimals def test_get_balances(self, factory, swap, pool_size): - assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] + [0] * ( - MAX_COINS - pool_size - ) + assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] @pytest.mark.only_for_pool_type(0) def test_get_underlying_balances(self, factory, swap): @@ -58,11 +54,6 @@ def test_get_A(self, factory, swap): def test_get_fees(self, factory, swap): assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) - def test_get_admin_balances(self, factory, swap, pool_size): - assert factory.get_admin_balances(swap.address) == [swap.admin_balances(i) for i in range(pool_size)] + [ - 0 - ] * (MAX_COINS - pool_size) - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_get_coin_indices(self, factory, swap, sending, receiving, pool_tokens): i, j, is_underlying = factory.get_coin_indices( @@ -71,8 +62,8 @@ def test_get_coin_indices(self, factory, swap, sending, receiving, pool_tokens): assert i == sending assert j == receiving - def test_get_implementation_address(self, factory, swap, amm_implementation_plain): - assert factory.get_implementation_address(swap.address) == amm_implementation_plain.address + def test_get_implementation_address(self, factory, swap, amm_implementation): + assert factory.get_implementation_address(swap.address) == amm_implementation.address def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is False @@ -95,16 +86,12 @@ def test_find_pool_for_coins_underlying(self, factory, swap, underlying_tokens, def test_get_meta_n_coins(self, factory, swap): assert factory.get_meta_n_coins(swap.address) == (2, 4) - def test_get_underlying_coins(self, factory, swap, underlying_tokens, zero_address): + def test_get_underlying_coins(self, factory, swap, underlying_tokens): tokens = [underlying_tokens[0]] + underlying_tokens[2:] - assert factory.get_underlying_coins(swap.address) == [t.address for t in tokens] + [zero_address] * ( - MAX_COINS - len(tokens) - ) + assert factory.get_underlying_coins(swap.address) == [t.address for t in tokens] def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): - assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals + [0] * ( - MAX_COINS - len(base_pool_decimals) - 1 - ) + assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals def test_get_metapool_rates(self, factory, swap, base_pool): assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] From 86eaaa43e34f8f74e7891dd82716181048159d88 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 11 Jul 2023 09:11:02 +0200 Subject: [PATCH 081/337] fix dy arrays --- contracts/main/CurveStableSwap2NG.vy | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index f19550ba..b18b15f1 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -538,7 +538,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: if i == N_COINS_128: break - result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) return result diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 8715724f..37573d55 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -273,8 +273,8 @@ def get_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) if self.pool_data[_pool].base_pool != empty(address): - balances[0] = CurvePool(_pool).balances(0) - balances[1] = CurvePool(_pool).balances(1) + balances.append(CurvePool(_pool).balances(0)) + balances.append(CurvePool(_pool).balances(1)) return balances n_coins: uint256 = self.pool_data[_pool].n_coins @@ -283,7 +283,7 @@ def get_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: if i == n_coins: break - balances[i] = CurvePool(_pool).balances(i) + balances.append(CurvePool(_pool).balances(i)) return balances @@ -351,7 +351,7 @@ def get_admin_balances(_pool: address) -> DynArray[uint256, MAX_COINS]: for i in range(MAX_COINS): if i == n_coins: break - admin_balances[i] = CurvePool(_pool).admin_balances(i) + admin_balances.append(CurvePool(_pool).admin_balances(i)) return admin_balances @@ -575,7 +575,7 @@ def deploy_plain_pool( for j in range(i, i + MAX_COINS): if (j + 1) == n_coins: break - swappable_coin: address = _coins[j] + swappable_coin: address = _coins[j + 1] key: uint256 = (convert(coin, uint256) ^ convert(swappable_coin, uint256)) length = self.market_counts[key] self.markets[key][length] = pool From 3a447f908cfc4ba065191f64e0f4054fbd466c03 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 11 Jul 2023 09:51:40 +0200 Subject: [PATCH 082/337] fix meta --- contracts/main/CurveStableSwap2NG.vy | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 49 ++++++++++++---------- tests/fixtures/factory.py | 4 +- tests/fixtures/pools.py | 17 ++++---- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index b18b15f1..067acea2 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -288,7 +288,7 @@ def __init__( for i in range(MAX_COINS): if i == __base_n_coins: break - self.is_rebasing[_base_coins[i]] = _is_rebasing[i+1] + self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] # Approval needed for add_liquidity operation on base pool in _exchange_underlying ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 37573d55..d3457b9f 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -233,17 +233,16 @@ def get_underlying_decimals(_pool: address) -> DynArray[uint256, MAX_COINS]: pool_decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) pool_decimals = self.pool_data[_pool].decimals decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - decimals[0] = pool_decimals[0] + decimals.append(pool_decimals[0]) base_pool: address = self.pool_data[_pool].base_pool packed_decimals: uint256 = self.base_pool_data[base_pool].decimals for i in range(MAX_COINS): - unpacked: uint256 = (packed_decimals >> 8 * i) % 256 if unpacked == 0: break - decimals[i+1] = unpacked + decimals.append(unpacked) return decimals @@ -595,6 +594,7 @@ def deploy_metapool( _fee: uint256, _ma_exp_time: uint256, _implementation_idx: uint256 = 0, + _asset_type: uint8 = 0, _method_id: bytes4 = empty(bytes4), _oracle: address = empty(address), _is_rebasing: bool = False @@ -624,7 +624,9 @@ def deploy_metapool( """ assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" - assert self.base_pool_data[_base_pool].coins[0] != empty(address), "Base pool is not added" + + base_pool_n_coins: uint256 = len(self.base_pool_data[_base_pool].coins) + assert base_pool_n_coins != 0, "Base pool is not added" implementation: address = self.pool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" @@ -635,20 +637,22 @@ def deploy_metapool( # combine _coins's _is_rebasing and basepool coins _is_rebasing: base_pool_is_rebasing: DynArray[bool, MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing - is_rebasing: DynArray[bool, MAX_COINS] = empty(DynArray[bool, MAX_COINS] ) - is_rebasing[0] = _is_rebasing - for i in range(1, MAX_COINS): + is_rebasing: DynArray[bool, MAX_COINS] = [_is_rebasing, False] + + base_pool_asset_types: DynArray[uint8, MAX_COINS] = self.base_pool_data[_base_pool].asset_types + asset_types: DynArray[uint8, MAX_COINS] = [_asset_type, 0] - if i == MAX_COINS: + for i in range(0, MAX_COINS): + if i == base_pool_n_coins: break - # is_rebasing[1] is for base_pool's coin[0] - is_rebasing[i] = base_pool_is_rebasing[i - 1] + is_rebasing.append(base_pool_is_rebasing[i]) + asset_types.append(base_pool_asset_types[i]) _coins: DynArray[address, MAX_COINS] = [_coin, self.base_pool_data[_base_pool].lp_token] - _rate_multipliers: DynArray[uint256, MAX_COINS] = [10 ** (36 - decimals)] - _method_ids: DynArray[bytes4, MAX_COINS] = [_method_id] - _oracles: DynArray[address, MAX_COINS] = [_oracle] + _rate_multipliers: DynArray[uint256, MAX_COINS] = [10 ** (36 - decimals), 10 ** 18] + _method_ids: DynArray[bytes4, MAX_COINS] = [_method_id, empty(bytes4)] + _oracles: DynArray[address, MAX_COINS] = [_oracle, empty(address)] pool: address = create_from_blueprint( implementation, @@ -664,7 +668,7 @@ def deploy_metapool( _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] _oracles, # _oracles: DynArray[address, MAX_COINS] - _is_rebasing, # _is_rebasing: DynArray[bool, MAX_COINS] + is_rebasing, # is_rebasing: DynArray[bool, MAX_COINS] code_offset=3 ) @@ -680,14 +684,15 @@ def deploy_metapool( self.pool_data[pool].decimals = [decimals, 0, 0, 0, 0, 0, 0, 0] self.pool_data[pool].n_coins = 2 self.pool_data[pool].base_pool = _base_pool - self.pool_data[pool].coins[0] = _coin - self.pool_data[pool].coins[1] = self.base_pool_data[_base_pool].lp_token + self.pool_data[pool].coins = [_coin, self.base_pool_data[_base_pool].lp_token] self.pool_data[pool].implementation = implementation is_finished: bool = False + swappable_coin: address = empty(address) for i in range(MAX_COINS): - swappable_coin: address = self.base_pool_data[_base_pool].coins[i] - if swappable_coin == empty(address): + if i < len(self.base_pool_data[_base_pool].coins): + swappable_coin = self.base_pool_data[_base_pool].coins[i] + else: is_finished = True swappable_coin = base_lp_token @@ -695,6 +700,7 @@ def deploy_metapool( length = self.market_counts[key] self.markets[key][length] = pool self.market_counts[key] = length + 1 + if is_finished: break @@ -740,7 +746,7 @@ def add_base_pool( @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing """ assert msg.sender == self.admin # dev: admin-only function - assert self.base_pool_data[_base_pool].coins[0] == empty(address) # dev: pool exists + assert len(self.base_pool_data[_base_pool].coins) == 0 # dev: pool exists assert _n_coins < MAX_COINS # dev: base pool can only have (MAX_COINS - 1) coins. # add pool to pool_list @@ -757,8 +763,9 @@ def add_base_pool( if i == _n_coins: break coin: address = coins[i] - self.base_pool_data[_base_pool].coins[i] = coin - self.base_pool_data[_base_pool].is_rebasing[i] = _is_rebasing[i] + self.base_pool_data[_base_pool].coins.append(coin) + self.base_pool_data[_base_pool].asset_types.append(_asset_types[i]) + self.base_pool_data[_base_pool].is_rebasing.append(_is_rebasing[i]) self.base_pool_assets[coin] = True decimals += (ERC20(coin).decimals() << i*8) self.base_pool_data[_base_pool].decimals = decimals diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 7cc5f8f8..6435a7dd 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -67,9 +67,9 @@ def add_base_pool( base_pool.address, base_pool_lp_token.address, [t.address for t in base_pool_tokens], - 0, + [0] * len(base_pool_tokens), len(base_pool_tokens), - [False] * 8, + [False] * len(base_pool_tokens), ) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 510a7aad..42c441b4 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -68,37 +68,37 @@ def swap( elif pool_type == 1: base_pool = request.getfixturevalue("base_pool") underlying_tokens = request.getfixturevalue("underlying_tokens") - amm_interface_meta = request.getfixturevalue("amm_interface_meta") + amm_interface_meta = request.getfixturevalue("amm_interface") _ = request.getfixturevalue("add_base_pool") - _ = request.getfixturevalue("set_meta_implementations") + _ = request.getfixturevalue("set_pool_implementations") A = 2000 fee = 1000000 method_id = bytes(b"") oracle = zero_address - # asset_type = 0 # 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + asset_type = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing is_rebasing = False metapool_token_type = pool_token_types[0] if metapool_token_type == 0: A = 2000 fee = 1000000 - # asset_type = 0 + asset_type = 0 elif metapool_token_type == 1: A = 1000 fee = 3000000 - # asset_type = 1 + asset_type = 1 elif metapool_token_type == 2: A = 1000 fee = 3000000 - # asset_type = 1 + asset_type = 2 method_id = oracle_method_id oracle = underlying_tokens[0].address elif metapool_token_type == 3: A = 500 fee = 4000000 - # asset_type = 1 + asset_type = 3 is_rebasing = True pool = factory.deploy_metapool( @@ -109,9 +109,10 @@ def swap( A, fee, 866, + 0, + 0, method_id, oracle, - 0, is_rebasing, ) From 6b840c9807cc88c2becafc7f40918193ae31eace Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 11 Jul 2023 09:52:11 +0200 Subject: [PATCH 083/337] uncomment --- contracts/main/CurveStableSwap2NG.vy | 84 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwap2NG.vy index 067acea2..ec3da0db 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwap2NG.vy @@ -1890,60 +1890,60 @@ def stored_rates(i: uint256) -> uint256: # --------------------------- AMM Admin Functions ---------------------------- -# @external -# def ramp_A(_future_A: uint256, _future_time: uint256): -# assert msg.sender == factory.admin() # dev: only owner -# assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME -# assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time +@external +def ramp_A(_future_A: uint256, _future_time: uint256): + assert msg.sender == factory.admin() # dev: only owner + assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME + assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time -# _initial_A: uint256 = self._A() -# _future_A_p: uint256 = _future_A * A_PRECISION + _initial_A: uint256 = self._A() + _future_A_p: uint256 = _future_A * A_PRECISION -# assert _future_A > 0 and _future_A < MAX_A -# if _future_A_p < _initial_A: -# assert _future_A_p * MAX_A_CHANGE >= _initial_A -# else: -# assert _future_A_p <= _initial_A * MAX_A_CHANGE + assert _future_A > 0 and _future_A < MAX_A + if _future_A_p < _initial_A: + assert _future_A_p * MAX_A_CHANGE >= _initial_A + else: + assert _future_A_p <= _initial_A * MAX_A_CHANGE -# self.initial_A = _initial_A -# self.future_A = _future_A_p -# self.initial_A_time = block.timestamp -# self.future_A_time = _future_time + self.initial_A = _initial_A + self.future_A = _future_A_p + self.initial_A_time = block.timestamp + self.future_A_time = _future_time -# log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) + log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) -# @external -# def stop_ramp_A(): -# assert msg.sender == factory.admin() # dev: only owner +@external +def stop_ramp_A(): + assert msg.sender == factory.admin() # dev: only owner -# current_A: uint256 = self._A() -# self.initial_A = current_A -# self.future_A = current_A -# self.initial_A_time = block.timestamp -# self.future_A_time = block.timestamp -# # now (block.timestamp < t1) is always False, so we return saved A + current_A: uint256 = self._A() + self.initial_A = current_A + self.future_A = current_A + self.initial_A_time = block.timestamp + self.future_A_time = block.timestamp + # now (block.timestamp < t1) is always False, so we return saved A -# log StopRampA(current_A, block.timestamp) + log StopRampA(current_A, block.timestamp) -# @external -# def apply_new_fee(_new_fee: uint256): +@external +def apply_new_fee(_new_fee: uint256): -# assert msg.sender == factory.admin() -# assert _new_fee <= MAX_FEE -# self.fee = _new_fee + assert msg.sender == factory.admin() + assert _new_fee <= MAX_FEE + self.fee = _new_fee -# log ApplyNewFee(_new_fee) + log ApplyNewFee(_new_fee) -# @external -# def set_ma_exp_time(_ma_exp_time: uint256): -# """ -# @notice Set the moving average window of the price oracle. -# @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) -# """ -# assert msg.sender == factory.admin() # dev: only owner -# assert _ma_exp_time != 0 +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == factory.admin() # dev: only owner + assert _ma_exp_time != 0 -# self.ma_exp_time = _ma_exp_time + self.ma_exp_time = _ma_exp_time From 553ab226ed3bdeeab77a72c2d9fa626bb6b3651b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:59:45 +0200 Subject: [PATCH 084/337] wip price oracle implementation for dynamic N_COINS --- contracts/main/CurveStableSwapMetaNG.vy | 1964 ----------------- ...eStableSwap2NG.vy => CurveStableSwapNG.vy} | 132 +- 2 files changed, 87 insertions(+), 2009 deletions(-) delete mode 100644 contracts/main/CurveStableSwapMetaNG.vy rename contracts/main/{CurveStableSwap2NG.vy => CurveStableSwapNG.vy} (94%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy deleted file mode 100644 index c7b9f4d0..00000000 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ /dev/null @@ -1,1964 +0,0 @@ -# @version 0.3.9 -""" -@title CurveStableSwap2NG -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice 2 coin pool implementation with no rehypothecation, i.e. tokens are not - deposited into other contracts. Supports only token pairs that are - similarly priced (or the underlying is similarly priced). -@dev ERC20 support for return True/revert, return True/False, return None - ERC20 tokens can have arbitrary decimals (<=18). - Additional features include: - 1. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) - Note: Oracle precision _must_ be 10**18. - 2. Adds oracles based on AMM State Price (and _not_ last traded price). - 3. Adds `get_dx`: Similar to `get_dy` which returns an expected output - of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected - input of coin[i] for an output amount of coin[j]. - 4. Adds `get_dx_underlying`. - 5. Adds `exchange_received` (expects ERC20 token transferred in and just swaps.) - 6. Adds `exchange_extended` (swap with callback) -""" - -from vyper.interfaces import ERC20 - -implements: ERC20 - -# ------------------------------- Interfaces --------------------------------- - -interface Factory: - def convert_metapool_fees() -> bool: nonpayable - def get_fee_receiver(_pool: address) -> address: view - def admin() -> address: view - def views_implementation() -> address: view - -interface ERC1271: - def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view - -interface Curve: - def coins(i: uint256) -> address: view - def get_virtual_price() -> uint256: view - def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view - def fee() -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - -interface Curve2: - def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view - def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable - -interface Curve3: - def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view - def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable - -interface Curve4: - def calc_token_amount(amounts: uint256[4], deposit: bool) -> uint256: view - def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable - -interface StableSwapViews: - def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy_underlying( - i: int128, j: int128, dy: uint256, pool: address - ) -> uint256: view - def get_dx_underlying( - i: int128, j: int128, dy: uint256, pool: address - ) -> uint256: view - def calc_token_amount( - _amounts: uint256[MAX_COINS], - _is_deposit: bool, - _pool: address - ) -> uint256: view - -# --------------------------------- Events ----------------------------------- - -event Transfer: - sender: indexed(address) - receiver: indexed(address) - value: uint256 - -event Approval: - owner: indexed(address) - spender: indexed(address) - value: uint256 - -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_id: int128 - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: uint256[N_COINS] - fees: uint256[N_COINS] - invariant: uint256 - token_supply: uint256 - -event RampA: - old_A: uint256 - new_A: uint256 - initial_time: uint256 - future_time: uint256 - -event StopRampA: - A: uint256 - t: uint256 - -event ApplyNewFee: - fee: uint256 - - -# ---------------------------- Pool Variables -------------------------------- - -MAX_COINS: constant(uint256) = 8 - -N_COINS: public(constant(uint256)) = 2 -N_COINS_128: constant(int128) = 2 -MAX_COIN: constant(int128) = N_COINS - 1 -PRECISION: constant(uint256) = 10 ** 18 -PERMISSIONED: public(constant(bool)) = False # Implementation contains permissionless tokens - -# token: is_rebasing flag -is_rebasing: HashMap[address, bool] - -BASE_POOL: public(immutable(address)) -BASE_N_COINS: public(immutable(uint256)) -BASE_COINS: public(immutable(address[MAX_COINS])) - -factory: public(immutable(Factory)) -coins: public(immutable(address[N_COINS])) -stored_balances: uint256[N_COINS] -fee: public(uint256) # fee * 1e10 -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 - -# ---------------------- Pool Amplification Parameters ----------------------- - -A_PRECISION: constant(uint256) = 100 -MAX_A: constant(uint256) = 10 ** 6 -MAX_A_CHANGE: constant(uint256) = 10 - -initial_A: public(uint256) -future_A: public(uint256) -initial_A_time: public(uint256) -future_A_time: public(uint256) - -# ---------------------------- Admin Variables ------------------------------- - -ADMIN_FEE: constant(uint256) = 5000000000 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 -MIN_RAMP_TIME: constant(uint256) = 86400 -admin_balances: public(uint256[N_COINS]) - -# ----------------------- Oracle Specific vars ------------------------------- - -rate_multipliers: uint256 -# [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: uint256 - -last_prices_packed: uint256 # [last_price, ma_price] -ma_exp_time: public(uint256) -ma_last_time: public(uint256) - -# shift(2**32 - 1, 224) -ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 - -# ----------------------- ERC20 Specific vars -------------------------------- - -name: public(immutable(String[64])) -symbol: public(immutable(String[32])) -decimals: public(constant(uint8)) = 18 -version: public(constant(String[8])) = "v7.0.0" - -balanceOf: public(HashMap[address, uint256]) -allowance: public(HashMap[address, HashMap[address, uint256]]) -totalSupply: public(uint256) -nonces: public(HashMap[address, uint256]) - -# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 -ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") -EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - -VERSION_HASH: constant(bytes32) = keccak256(version) -NAME_HASH: immutable(bytes32) -CACHED_CHAIN_ID: immutable(uint256) -salt: public(immutable(bytes32)) -CACHED_DOMAIN_SEPARATOR: immutable(bytes32) - - -# ------------------------------ AMM Setup ----------------------------------- - - -@external -def __init__( - _name: String[32], - _symbol: String[10], - _coin: address, - _rate_multiplier: uint256, - _A: uint256, - _fee: uint256, - _ma_exp_time: uint256, - _method_id: bytes4, - _oracle: address, - _is_rebasing: bool[MAX_COINS], # [_coin, base_coin_0, base_coin_1, ...] - _base_pool: address, - _base_lp_token: address, - _base_coins: address[MAX_COINS], # base pool can have maximally (MAX_COINS - 1) coins -): - """ - @notice Initialize the pool contract - @param _name Name of the new plain pool. - @param _symbol Symbol for the new plain pool. - @param _coins List of addresses of the coins being used in the pool. - @param _A Amplification co-efficient - a lower value here means - less tolerance for imbalance within the pool's assets. - Suggested values include: - * Uncollateralized algorithmic stablecoins: 5-10 - * Non-redeemable, collateralized assets: 100 - * Redeemable assets: 200-400 - @param _fee Trade fee, given as an integer with 1e10 precision. The - the maximum is 1% (100000000). - 50% of the fee is distributed to veCRV holders. - @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) - Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 - @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures - of the oracle addresses that gives rate oracles. - Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] - @param _oracles Array of rate oracle addresses. - @param _is_rebasing If any of the coins rebases, then this should be set to True. - @param _base_pool Address of the base pool. - @param _base_lp_token Address of the basepool's lp token. - @param _base_coins Addresses of coins in the base pool. - """ - name = _name - symbol = _symbol - coins = [_coin, _base_lp_token] - factory = Factory(msg.sender) - - BASE_COINS = _base_coins - BASE_POOL = _base_pool - - self.rate_multipliers = _rate_multiplier - self.oracles = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) - self.is_rebasing[_coin] = _is_rebasing[0] - self.is_rebasing[_base_lp_token] = False - - base_n_coins: uint256 = 0 - for coin in _base_coins: - - if coin == empty(address): - break - - ERC20(coin).approve(BASE_POOL, max_value(uint256)) - base_n_coins += 1 - - # _is_rebasing from idx 1 and onwards is for base pool's coins - self.is_rebasing[coin] = _is_rebasing[base_n_coins] - - BASE_N_COINS = base_n_coins - - A: uint256 = _A * A_PRECISION - self.initial_A = A - self.future_A = A - self.fee = _fee - - assert _ma_exp_time != 0 - self.ma_exp_time = _ma_exp_time - self.last_prices_packed = self.pack_prices(10**18, 10**18) - self.ma_last_time = block.timestamp - - # EIP712 - NAME_HASH = keccak256(name) - salt = block.prevhash - CACHED_CHAIN_ID = chain.id - CACHED_DOMAIN_SEPARATOR = keccak256( - _abi_encode( - EIP712_TYPEHASH, - NAME_HASH, - VERSION_HASH, - chain.id, - self, - salt, - ) - ) - - # fire a transfer event so block explorers identify the contract as an ERC20 - log Transfer(empty(address), self, 0) - - -# ------------------ Token transfers in and out of the AMM ------------------- - - -@internal -def _transfer_in( - coin: address, - dx: uint256, - dy: uint256, - callbacker: address, - callback_sig: bytes32, - sender: address, - receiver: address, -) -> uint256: - """ - @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig` - if it is not empty. - @dev The callback sig must have the following args: - sender: address - receiver: address - coin: address - dx: uint256 - dy: uint256 - The `dy` that the pool enforces is actually min_dy. - Callback only occurs for `exchange_extended`. - Callback cannot happen for `_use_eth` = True. - @dev If callback_sig is empty, `_transfer_in` does a transferFrom. - @params _coin address of the coin to transfer in. - @params dx amount of `_coin` to transfer into the pool. - @params dy amount of `_coin` to transfer out of the pool. - @params mvalue msg.value if the transfer is ETH, 0 otherwise. - @params callbacker address to call `callback_sig` on. - @params callback_sig signature of the callback function. - @params sender address to transfer `_coin` from. - @params receiver address to transfer `_coin` to. - @params use_eth True if the transfer is ETH, False otherwise. - """ - _dx: uint256 = dx - - # --------------------- Start Callback Handling ---------------------- - - initial_x: uint256 = ERC20(coin).balanceOf(self) - - if callback_sig == empty(bytes32): - - assert ERC20(coin).transferFrom( - sender, self, _dx, default_return_value=True - ) - - else: - - raw_call( - callbacker, - concat( - slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coin, _dx, dy) - ) - ) - - # If the coin is a fee-on-transfer token, transferring `_dx` amount can - # result in the pool receiving slightly less amount. So: recalculate dx - - _dx = ERC20(coin).balanceOf(self) - initial_x - - assert _dx > 0 # dev: pool received 0 tokens # TODO: check! - - # -------------------- End Callback Handling ------------------------- - - return _dx - - -# -------------------------- AMM Special Methods ----------------------------- - - -@view -@internal -def _stored_rates() -> uint256[N_COINS]: - """ - @notice Gets rate multipliers for each coin. - @dev If the coin has a rate oracle that has been properly initialised, - this method queries that rate by static-calling an external - contract. - """ - - rates: uint256[N_COINS] = [ - self.rate_multipliers, - Curve(BASE_POOL).get_virtual_price() - ] - oracles: uint256 = self.oracles - - if oracles == 0: - return rates - - # NOTE: assumed that response is of precision 10**18 - response: Bytes[32] = raw_call( - convert(oracles % 2**160, address), - _abi_encode(oracles & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ) - - assert len(response) != 0 - rates[0] = rates[0] * convert(response, uint256) / PRECISION - - return rates - - -@view -@internal -def _balances() -> uint256[N_COINS]: - """ - @notice Calculates the pool's balances _excluding_ the admin's balances. - @dev This method ensures LPs keep all rebases and admin only claims swap fees. - """ - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] - - return result - - -@internal -def _increase_balances(balances: uint256[N_COINS]): - """ - @notice Increases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer into the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] += balances[i] - self.stored_balances = stored_balances - - -@internal -def _decrease_balances(balances: uint256[N_COINS]): - """ - @notice Decreases self.stored_balances by `balances` amount - @dev This is an internal accounting method and must be called whenever there - is an ERC20 token transfer out of the pool. - """ - stored_balances: uint256[N_COINS] = self.stored_balances - for i in range(N_COINS): - stored_balances[i] -= balances[i] - self.stored_balances = stored_balances - - -# -------------------------- AMM Main Functions ------------------------------ - - -@external -@nonreentrant('lock') -def exchange( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - return self._exchange( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool, - _sender: address, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two coins after a callback - @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth). Users of this method are dex aggregators, - arbitrageurs, or other users who do not wish to grant approvals to the contract. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: No callback specified - return self._exchange( - _sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, # <---------------------------- callbacker is msg.sender. - _cb, - False - ) - - -@external -@nonreentrant('lock') -def exchange_received( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two coins without transferring token in - @dev The contract swaps tokens based on a change in balance of coin[i]. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `exchane_received`. - The method is non-payable: does not accept native token. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert self.is_rebasing[coins[i]] # dev: rebasing tokens are not supported - return self._exchange( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - True, # <--------------------------------------- swap optimistically. - ) - - -@external -@nonreentrant('lock') -def exchange_underlying( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - For MetaNG: native token wrapping/unwrapping is disabled; this - parameter can be whatever. - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_underlying_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: no callback specified - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, - _cb, - False - ) - - -@external -@nonreentrant('lock') -def exchange_underlying_received( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _use_eth: bool, - _receiver: address, -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - True - ) - - -@external -@nonreentrant('lock') -def add_liquidity( - _amounts: uint256[N_COINS], - _min_mint_amount: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _receiver Address that owns the minted LP tokens - @return Amount of LP tokens received by depositing - """ - amp: uint256 = self._A() - old_balances: uint256[N_COINS] = self._balances() - rates: uint256[N_COINS] = self._stored_rates() - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.totalSupply - new_balances: uint256[N_COINS] = old_balances - - # -------------------------- Do Transfers In ----------------------------- - - for i in range(N_COINS): - - if _amounts[i] > 0: - - new_balances[i] += self._transfer_in( - coins[i], - _amounts[i], - 0, - empty(address), - empty(bytes32), - msg.sender, - empty(address), - ) - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - # Add incoming balance - self._increase_balances(new_balances) - - # ------------------------------------------------------------------------ - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - mint_amount: uint256 = 0 - - if total_supply > 0: - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) - - else: - - mint_amount = D1 # Take the dust if there was any - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[_receiver] += mint_amount - self.totalSupply = total_supply - log Transfer(empty(address), _receiver, mint_amount) - - log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) - - return mint_amount - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _burn_amount: uint256, - i: int128, - _min_received: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _burn_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @param _min_received Minimum amount of coin to receive - @param _receiver Address that receives the withdrawn coins - @return Amount of coin received - """ - dy: uint256[3] = self._calc_withdraw_one_coin(_burn_amount, i) - assert dy[0] >= _min_received, "Not enough coins removed" - - self.admin_balances[i] += dy[1] * ADMIN_FEE / FEE_DENOMINATOR - total_supply: uint256 = self.totalSupply - _burn_amount - self.totalSupply = total_supply - self.balanceOf[msg.sender] -= _burn_amount - - log Transfer(msg.sender, empty(address), _burn_amount) - - assert ERC20(coins[i]).transfer(_receiver, dy[0], default_return_value=True) - - # Decrease coin[i] balance in self.stored_balances - self.stored_balances[i] -= dy[0] - - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) - - self.save_p_from_price(dy[2]) - - return dy[0] - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: uint256[N_COINS], - _max_burn_amount: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param _amounts List of amounts of underlying coins to withdraw - @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal - @param _receiver Address that receives the withdrawn coins - @return Actual amount of the LP token burned in the withdrawal - """ - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - new_balances: uint256[N_COINS] = old_balances - for i in range(N_COINS): - if _amounts[i] != 0: - new_balances[i] -= _amounts[i] - assert ERC20(coins[i]).transfer( - _receiver, _amounts[i], default_return_value=True - ) - - # Decrease balances in self.stored_balances - self._decrease_balances(_amounts) - - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - - fees: uint256[N_COINS] = empty(uint256[N_COINS]) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(N_COINS): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - D2: uint256 = self.get_D_mem(rates, new_balances, amp) - - self.save_p(new_balances, amp, D2) - - total_supply: uint256 = self.totalSupply - burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 - assert burn_amount > 1 # dev: zero tokens burned - assert burn_amount <= _max_burn_amount, "Slippage screwed you" - - total_supply -= burn_amount - self.totalSupply = total_supply - self.balanceOf[msg.sender] -= burn_amount - log Transfer(msg.sender, empty(address), burn_amount) - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) - - return burn_amount - - -@external -@nonreentrant('lock') -def remove_liquidity( - _burn_amount: uint256, - _min_amounts: uint256[N_COINS], - _use_eth: bool = False, - _receiver: address = msg.sender, - _claim_admin_fees: bool = True, -) -> uint256[N_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _burn_amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @param _receiver Address that receives the withdrawn coins - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.totalSupply - amounts: uint256[N_COINS] = empty(uint256[N_COINS]) - balances: uint256[N_COINS] = self._balances() - - for i in range(N_COINS): - value: uint256 = balances[i] * _burn_amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts[i] = value - assert ERC20(coins[i]).transfer(_receiver, value, default_return_value=True) - - # Decrease balances in self.stored_balances - self._decrease_balances(amounts) - - total_supply -= _burn_amount - self.balanceOf[msg.sender] -= _burn_amount - self.totalSupply = total_supply - log Transfer(msg.sender, empty(address), _burn_amount) - - log RemoveLiquidity(msg.sender, amounts, empty(uint256[N_COINS]), total_supply) - - # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. - if _claim_admin_fees: - self._withdraw_admin_fees() - - return amounts - - -@external -def withdraw_admin_fees(): - """ - @notice Claim admin fees. Callable by anyone. - """ - self._withdraw_admin_fees() - - -# ------------------------ AMM Internal Functions ---------------------------- - - -@internal -def _exchange_core( - dx: uint256, - x: uint256, - xp: uint256[N_COINS], - rates: uint256[N_COINS], - i: int128, - j: int128, -) -> uint256: - - amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(i, j, x, xp, amp, D) - - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - - self.admin_balances[j] += ( - dy_fee * ADMIN_FEE / FEE_DENOMINATOR - ) * PRECISION / rates[j] - - # D is not changed because we did not apply a fee - self.save_p([x, y], amp, D) - - return dy - - -@internal -def _exchange( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - callbacker: address, - callback_sig: bytes32, - expect_optimistic_transfer: bool = False -) -> uint256: - - assert i != j # dev: coin index out of range - assert _dx > 0 # dev: do not exchange 0 coins - - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - - # --------------------------- Do Transfer in ----------------------------- - - dx: uint256 = 0 - - if expect_optimistic_transfer: - - # This branch is never reached for rebasing tokens - pool_x_balance: uint256 = ERC20(coins[i]).balanceOf(self) - dx = pool_x_balance - self.stored_balances[i] - - assert dx == _dx, "Pool did not receive tokens for swap" - - else: - - # `dx` is whatever the pool received after ERC20 transfer: - dx = self._transfer_in( - coins[i], - _dx, - _min_dy, - callbacker, - callback_sig, - sender, - receiver - ) - - # Update stored balances - self.stored_balances[i] += dx - - # ------------------------------------------------------------------------ - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - dy: uint256 = self._exchange_core(dx, x, xp, rates, i, j) - assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" - - # --------------------------- Do Transfer out ---------------------------- - - assert ERC20(coins[j]).transfer(receiver, dy, default_return_value=True) - - # Update Stored Balances: - self.stored_balances[j] -= dy - - # ------------------------------------------------------------------------ - - log TokenExchange(msg.sender, i, _dx, j, dy) - - return dy - - -@internal -def _exchange_underlying( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - callbacker: address, - callback_sig: bytes32, - expect_optimistic_transfer: bool = False -) -> uint256: - - rates: uint256[N_COINS] = self._stored_rates() - old_balances: uint256[N_COINS] = self._balances() - xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) - - dy: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - x: uint256 = 0 - input_coin: address = empty(address) - output_coin: address = empty(address) - - if i == 0: - input_coin = coins[0] - else: - base_i = i - MAX_COIN # if i == 1, this reverts - meta_i = 1 - input_coin = BASE_COINS[base_i] - if j == 0: - output_coin = coins[0] - else: - base_j = j - MAX_COIN # if j == 1, this reverts - meta_j = 1 - output_coin = BASE_COINS[base_j] - - # --------------------------- Do Transfer in ----------------------------- - - dx_w_fee: uint256 = 0 - - if expect_optimistic_transfer: - - assert self.is_rebasing[input_coin] # dev: rebasing coins not supported - - # This branch is never reached for rebasing tokens - if input_coin == BASE_COINS[base_i]: - # we expect base_coin's balance to be 0. So swap whatever base_coin's - # balance the pool has: - dx_w_fee = ERC20(input_coin).balanceOf(self) - else: - dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] - assert dx_w_fee == _dx - self.stored_balances[meta_i] += dx_w_fee - - else: - - dx_w_fee = self._transfer_in( - input_coin, - _dx, - _min_dy, - callbacker, - callback_sig, - sender, - receiver, - ) - - # ------------------------------------------------------------------------ - - if i == 0 or j == 0: # meta swap - - if i == 0: - - x = xp[i] + dx_w_fee * rates[i] / PRECISION - - else: - - dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_COIN] / PRECISION - x += xp[MAX_COIN] - - dy = self._exchange_core(dx_w_fee, x, xp, rates, meta_i, meta_j) - - # Withdraw from the base pool if needed - if j > 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= _min_dy - - # Adjust stored balances: - self.stored_balances[meta_j] -= dy - - else: # base pool swap (user should swap at base pool for better gas) - - dy = ERC20(output_coin).balanceOf(self) - Curve(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # --------------------------- Do Transfer out ---------------------------- - - assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) - - # ------------------------------------------------------------------------ - - log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! - - return dy - - -@internal -def _withdraw_admin_fees(): - - amounts: uint256[N_COINS] = self.admin_balances - - if amounts[0] > 0: - - assert ERC20(coins[0]).transfer( - factory.address, - amounts[0], - default_return_value=True - ) - factory.convert_metapool_fees() - - if amounts[1] > 0: - - receiver: address = factory.get_fee_receiver(self) - assert ERC20(coins[1]).transfer( - receiver, - amounts[1], - default_return_value=True - ) - - self.admin_balances = empty(uint256[N_COINS]) - - # Decrease balances in self.stored_balances - self._decrease_balances(amounts) - - -# ------------------------ AMM Metapool Functions ---------------------------- - - -@internal -def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - - coin_i: address = coins[MAX_COIN] - x: uint256 = ERC20(coin_i).balanceOf(self) - - if BASE_N_COINS == 2: - - base_inputs: uint256[2] = empty(uint256[2]) - base_inputs[base_i] = dx - Curve2(BASE_POOL).add_liquidity(base_inputs, 0) - - if BASE_N_COINS == 3: - - base_inputs: uint256[3] = empty(uint256[3]) - base_inputs[base_i] = dx - Curve3(BASE_POOL).add_liquidity(base_inputs, 0) - - else: - - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - Curve4(BASE_POOL).add_liquidity(base_inputs, 0) - - return ERC20(coin_i).balanceOf(self) - x - - -@internal -@view -def _meta_calc_token_amounts_deposit( - dx: uint256, base_i: int128, meta_vprice: uint256 -) -> uint256: - - if BASE_N_COINS == 2: - - base_inputs: uint256[2] = empty(uint256[2]) - base_inputs[base_i] = dx - return Curve2(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION - - elif BASE_N_COINS == 3: - - base_inputs: uint256[3] = empty(uint256[3]) - base_inputs[base_i] = dx - return Curve3(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION - - else: - - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - return Curve4(BASE_POOL).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION - - -# --------------------------- AMM Math Functions ----------------------------- - -@internal -@pure -def newton_y(b: uint256, c: uint256, D: uint256, _y: uint256) -> uint256: - - y_prev: uint256 = 0 - y: uint256 = _y - - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def get_y( - i: int128, - j: int128, - x: uint256, - xp: uint256[N_COINS], - _amp: uint256, - _D: uint256 -) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS_128 # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS_128 - - amp: uint256 = _amp - D: uint256 = _D - if _D == 0: - amp = self._A() - D = self.get_D(xp, amp) - S_: uint256 = 0 - _x: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - for _i in range(N_COINS_128): - if _i == i: - _x = x - elif _i != j: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - y: uint256 = D - - return self.newton_y(b, c, D, y) - - -@pure -@internal -def get_D(_xp: uint256[N_COINS], _amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - for x in _xp: - S += x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = _amp * N_COINS - for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS - Dprev: uint256 = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) - # Equality with the precision of 1 - if D > Dprev: - if D - Dprev <= 1: - return D - else: - if Dprev - D <= 1: - return D - # convergence typically occurs in 4 rounds or less, this should be unreachable! - # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` - raise - - -@pure -@internal -def get_y_D(A: uint256, i: int128, xp: uint256[N_COINS], D: uint256) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS_128 # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = A * N_COINS - - for _i in range(N_COINS_128): - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - y: uint256 = D - - return self.newton_y(b, c, D, y) - - -@view -@internal -def _A() -> uint256: - """ - Handle ramping A up or down - """ - t1: uint256 = self.future_A_time - A1: uint256 = self.future_A - - if block.timestamp < t1: - A0: uint256 = self.initial_A - t0: uint256 = self.initial_A_time - # Expressions in uint256 cannot have negative numbers, thus "if" - if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) - else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) - - else: # when t1 == 0 or block.timestamp >= t1 - return A1 - - -@pure -@internal -def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: uint256[N_COINS] = empty(uint256[N_COINS]) - for i in range(N_COINS): - result[i] = _rates[i] * _balances[i] / PRECISION - return result - - -@view -@internal -def get_D_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS], _amp: uint256) -> uint256: - xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) - return self.get_D(xp, _amp) - - -@view -@internal -def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - rates: uint256[N_COINS] = self._stored_rates() - xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.totalSupply - D1: uint256 = D0 - _burn_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: uint256[N_COINS] = empty(uint256[N_COINS]) - - for j in range(N_COINS_128): - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] - if j == i: - dx_expected = xp_j * D1 / D0 - new_y - else: - dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - xp[i] = new_y - last_p: uint256 = 0 - if new_y > 0: - last_p = self._get_p(xp, amp, D1) - - return [dy, dy_0 - dy, last_p] - - -# -------------------------- AMM Price Methods ------------------------------- - - -@pure -@internal -def pack_prices(p1: uint256, p2: uint256) -> uint256: - assert p1 < 2**128 - assert p2 < 2**128 - return p1 | (p2 << 128) - - -@internal -@view -def _get_p(xp: uint256[N_COINS], amp: uint256, D: uint256) -> uint256: - # dx_0 / dx_1 only, however can have any number of coins in pool - ANN: uint256 = amp * N_COINS - Dr: uint256 = D / (N_COINS**N_COINS) - for i in range(N_COINS): - Dr = Dr * D / xp[i] - return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) - - -@internal -def save_p_from_price(last_price: uint256): - """ - Saves current price and its EMA - """ - if last_price != 0: - self.last_prices_packed = self.pack_prices(last_price, self._ma_price()) - if self.ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp - - -@internal -def save_p(xp: uint256[N_COINS], amp: uint256, D: uint256): - """ - Saves current price and its EMA - """ - self.save_p_from_price(self._get_p(xp, amp, D)) - - -@internal -@view -def _ma_price() -> uint256: - ma_last_time: uint256 = self.ma_last_time - - pp: uint256 = self.last_prices_packed - last_price: uint256 = pp & (2**128 - 1) - last_ema_price: uint256 = (pp >> 128) - - if ma_last_time < block.timestamp: - alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) - return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 - - else: - return last_ema_price - - -# ----------------------------- Math Utils ----------------------------------- - - -@internal -@pure -def exp(x: int256) -> uint256: - - """ - @dev Calculates the natural exponential function of a signed integer with - a precision of 1e18. - @notice Note that this function consumes about 810 gas units. The implementation - is inspired by Remco Bloemen's implementation under the MIT license here: - https://xn--2-umb.com/22/exp-ln. - @dev This implementation is derived from Snekmate, which is authored - by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. - https://github.com/pcaversaccio/snekmate - @param x The 32-byte variable. - @return int256 The 32-byte calculation result. - """ - value: int256 = x - - # If the result is `< 0.5`, we return zero. This happens when we have the following: - # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". - if (x <= -42139678854452767551): - return empty(uint256) - - # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. - # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". - assert x < 135305999368893231589, "wad_exp overflow" - - # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher - # intermediate precision and a binary base. This base conversion is a multiplication with - # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". - value = unsafe_div(x << 78, 5 ** 18) - - # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two - # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives - # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". - k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 - value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) - - # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, - # we will multiply by a scaling factor later. - y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) - p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ - 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) - - # We leave `p` in the "2 ** 192" base so that we do not have to scale it up - # again for the division. - q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) - q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) - q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) - - # The polynomial `q` has no zeros in the range because all its roots are complex. - # No scaling is required, as `p` is already "2 ** 96" too large. Also, - # `r` is in the range "(0.09, 0.25) * 2**96" after the division. - r: int256 = unsafe_div(p, q) - - # To finalise the calculation, we have to multiply `r` by: - # - the scale factor "s = ~6.031367120", - # - the factor "2 ** k" from the range reduction, and - # - the factor "1e18 / 2 ** 96" for the base conversion. - # We do this all at once, with an intermediate result in "2**213" base, - # so that the final right shift always gives a positive value. - - # Note that to circumvent Vyper's safecast feature for the potentially - # negative parameter value `r`, we first convert `r` to `bytes32` and - # subsequently to `uint256`. Remember that the EVM default behaviour is - # to use two's complement representation to handle signed integers. - return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) - - -# --------------------------- ERC20 Functionality ---------------------------- - -@view -@internal -def _domain_separator() -> bytes32: - if chain.id != CACHED_CHAIN_ID: - return keccak256( - _abi_encode( - EIP712_TYPEHASH, - NAME_HASH, - VERSION_HASH, - chain.id, - self, - salt, - ) - ) - return CACHED_DOMAIN_SEPARATOR - - -@internal -def _transfer(_from: address, _to: address, _value: uint256): - # # NOTE: vyper does not allow underflows - # # so the following subtraction would revert on insufficient balance - self.balanceOf[_from] -= _value - self.balanceOf[_to] += _value - - log Transfer(_from, _to, _value) - - -@external -def transfer(_to : address, _value : uint256) -> bool: - """ - @dev Transfer token for a specified address - @param _to The address to transfer to. - @param _value The amount to be transferred. - """ - self._transfer(msg.sender, _to, _value) - return True - - -@external -def transferFrom(_from : address, _to : address, _value : uint256) -> bool: - """ - @dev Transfer tokens from one address to another. - @param _from address The address which you want to send tokens from - @param _to address The address which you want to transfer to - @param _value uint256 the amount of tokens to be transferred - """ - self._transfer(_from, _to, _value) - - _allowance: uint256 = self.allowance[_from][msg.sender] - if _allowance != max_value(uint256): - self.allowance[_from][msg.sender] = _allowance - _value - - return True - - -@external -def approve(_spender : address, _value : uint256) -> bool: - """ - @notice Approve the passed address to transfer the specified amount of - tokens on behalf of msg.sender - @dev Beware that changing an allowance via this method brings the risk that - someone may use both the old and new allowance by unfortunate transaction - ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - @param _spender The address which will transfer the funds - @param _value The amount of tokens that may be transferred - @return bool success - """ - self.allowance[msg.sender][_spender] = _value - - log Approval(msg.sender, _spender, _value) - return True - - -@external -def permit( - _owner: address, - _spender: address, - _value: uint256, - _deadline: uint256, - _v: uint8, - _r: bytes32, - _s: bytes32 -) -> bool: - """ - @notice Approves spender by owner's signature to expend owner's tokens. - See https://eips.ethereum.org/EIPS/eip-2612. - @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 - @dev Supports smart contract wallets which implement ERC1271 - https://eips.ethereum.org/EIPS/eip-1271 - @param _owner The address which is a source of funds and has signed the Permit. - @param _spender The address which is allowed to spend the funds. - @param _value The amount of tokens to be spent. - @param _deadline The timestamp after which the Permit is no longer valid. - @param _v The bytes[64] of the valid secp256k1 signature of permit by owner - @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner - @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner - @return True, if transaction completes successfully - """ - assert _owner != empty(address) - assert block.timestamp <= _deadline - - nonce: uint256 = self.nonces[_owner] - digest: bytes32 = keccak256( - concat( - b"\x19\x01", - self._domain_separator(), - keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) - ) - ) - - if _owner.is_contract: - sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) - # reentrancy not a concern since this is a staticcall - assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL - else: - assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner - - self.allowance[_owner][_spender] = _value - self.nonces[_owner] = nonce + 1 - - log Approval(_owner, _spender, _value) - return True - - -@view -@external -def DOMAIN_SEPARATOR() -> bytes32: - """ - @notice EIP712 domain separator. - @return bytes32 Domain Separator set for the current chain. - """ - return self._domain_separator() - - -# ------------------------- AMM View Functions ------------------------------- - - -@view -@external -def last_price() -> uint256: - return self.last_prices_packed & (2**128 - 1) - - -@view -@external -def ema_price() -> uint256: - return (self.last_prices_packed >> 128) - - -@external -@view -def get_p() -> uint256: - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = self.get_D(xp, amp) - return self._get_p(xp, amp, D) - - -@external -@view -@nonreentrant('lock') -def price_oracle() -> uint256: - return self._ma_price() - - -@view -@external -def get_dx(i: int128, j: int128, dy: uint256) -> uint256: - """ - @notice Calculate the current input dx given output dy - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dy Amount of `j` being received after exchange - @return Amount of `i` predicted - """ - return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) - - -@view -@external -def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: - # TODO: Needs get_dx_underlying - return StableSwapViews(factory.views_implementation()).get_dx_underlying(i, j, dy, self) - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - """ - @notice Calculate the current output dy given input dx - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @return Amount of `j` predicted - """ - return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) - -@view -@external -def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: - """ - @notice Calculate the current output dy given input dx on underlying - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @return Amount of `j` predicted - """ - return StableSwapViews(factory.views_implementation()).get_dy_underlying(i, j, dx, self) - - -@view -@external -def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _burn_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_burn_amount, i)[0] - - -@view -@external -@nonreentrant('lock') -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = self.get_D(xp, amp) - # D is in the units similar to DAI (e.g. converted to precision 1e18) - # When balanced, D = n * x_u - total virtual value of the portfolio - return D * PRECISION / self.totalSupply - - -@view -@external -def calc_token_amount(_amounts: uint256[N_COINS], _is_deposit: bool) -> uint256: - """ - @notice Calculate addition or reduction in token supply from a deposit or withdrawal - @param _amounts Amount of each coin being deposited - @param _is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - amounts: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) - for i in range(MAX_COINS): - if i == N_COINS: - break - amounts[i] = _amounts[i] - - views: address = factory.views_implementation() - return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) - - -@view -@external -def admin_fee() -> uint256: - return ADMIN_FEE - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@external -def balances(i: uint256) -> uint256: - """ - @notice Get the current balance of a coin within the - pool, less the accrued admin fees - @param i Index value for the coin to query balance of - @return Token balance - """ - return self._balances()[i] - - -@view -@external -def get_balances() -> uint256[N_COINS]: - return self._balances() - - -@view -@external -def oracle(_idx: uint256) -> address: - return convert(self.oracles % 2**160, address) - - -@view -@external -def stored_rates(i: uint256) -> uint256: - return self._stored_rates()[i] - - -# --------------------------- AMM Admin Functions ---------------------------- - - -@external -def ramp_A(_future_A: uint256, _future_time: uint256): - assert msg.sender == factory.admin() # dev: only owner - assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME - assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time - - _initial_A: uint256 = self._A() - _future_A_p: uint256 = _future_A * A_PRECISION - - assert _future_A > 0 and _future_A < MAX_A - if _future_A_p < _initial_A: - assert _future_A_p * MAX_A_CHANGE >= _initial_A - else: - assert _future_A_p <= _initial_A * MAX_A_CHANGE - - self.initial_A = _initial_A - self.future_A = _future_A_p - self.initial_A_time = block.timestamp - self.future_A_time = _future_time - - log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) - - -@external -def stop_ramp_A(): - assert msg.sender == factory.admin() # dev: only owner - - current_A: uint256 = self._A() - self.initial_A = current_A - self.future_A = current_A - self.initial_A_time = block.timestamp - self.future_A_time = block.timestamp - # now (block.timestamp < t1) is always False, so we return saved A - - log StopRampA(current_A, block.timestamp) - - -@external -def apply_new_fee(_new_fee: uint256): - - assert msg.sender == factory.admin() - assert _new_fee <= MAX_FEE - self.fee = _new_fee - - log ApplyNewFee(_new_fee) - - -@external -def set_ma_exp_time(_ma_exp_time: uint256): - """ - @notice Set the moving average window of the price oracle. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) - """ - assert msg.sender == factory.admin() # dev: only owner - assert _ma_exp_time != 0 - - self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwap2NG.vy b/contracts/main/CurveStableSwapNG.vy similarity index 94% rename from contracts/main/CurveStableSwap2NG.vy rename to contracts/main/CurveStableSwapNG.vy index ec3da0db..46c8b2cc 100644 --- a/contracts/main/CurveStableSwap2NG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,13 +1,19 @@ # @version 0.3.9 """ -@title CurveStableSwap2NG +@title CurveStableSwapNG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice 2 coin pool implementation with no rehypothecation, i.e. tokens are not - deposited into other contracts. Supports only token pairs that are - similarly priced (or the underlying is similarly priced). -@dev ERC20 support for return True/revert, return True/False, return None - ERC20 tokens can have arbitrary decimals (<=18). +@notice Stableswap implementation for up to 8 coins with no rehypothecation, + i.e. tokens are not deposited into other contracts. Supports only + token pairs that are similarly priced. This contract also + supports metapools (2-coin pools where the second coin is an LP token). + The Pool contract also records exponential moving averages for coins + 1, 2 and 3 relative to coin 0. +@dev Supports: + 1. ERC20 support for return True/revert, return True/False, return None + 2. ERC20 tokens can have arbitrary decimals (<=18). + 3. ERC20 tokens that rebase (either positive or fee on transfer) + 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) Additional features include: 1. Support for rebasing tokens: but this disables `exchange_received`. @@ -190,7 +196,7 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: uint256 # [last_price, ma_price] +last_prices_packed: DynArray[uint256, MAX_COINS] # [last_price, ma_price] ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -286,14 +292,14 @@ def __init__( # because _coins[1] is base_lp_token and that does not rebase. for i in range(MAX_COINS): - if i == __base_n_coins: - break - self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] + if i < __base_n_coins: + self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] - # Approval needed for add_liquidity operation on base pool in _exchange_underlying - ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) + # Approval needed for add_liquidity operation on base pool in _exchange_underlying + ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - self.last_prices_packed = self.pack_prices(10**18, 10**18) # <--- TODO: check this! + if i < __n_coins: + self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) # <--- TODO: check this! # Only need rate_multiplier for coin paired against basepool's lp token: __rate_multipliers = [_rate_multipliers[0]] @@ -301,7 +307,10 @@ def __init__( else: __n_coins = len(_coins) - self.last_prices_packed = self.pack_prices(10**18, 10**18) # TODO: fix this since there are more than 2 prices! + for i in range(MAX_COINS): + if i == __n_coins: + break + self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) __rate_multipliers = _rate_multipliers N_COINS = __n_coins @@ -784,23 +793,26 @@ def remove_liquidity_one_coin( @param _receiver Address that receives the withdrawn coins @return Amount of coin received """ - dy: uint256[3] = self._calc_withdraw_one_coin(_burn_amount, i) - assert dy[0] >= _min_received, "Not enough coins removed" + dy: uint256 = 0 + fee: uint256 = 0 + p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) + assert dy >= _min_received, "Not enough coins removed" - self.admin_balances[i] += dy[1] * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR total_supply: uint256 = self.totalSupply - _burn_amount self.totalSupply = total_supply self.balanceOf[msg.sender] -= _burn_amount log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(i, dy[0], _use_eth, _receiver) + self._transfer_out(i, dy, _use_eth, _receiver) - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy[0], total_supply) + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, total_supply) - self.save_p_from_price(dy[2]) + self.save_p_from_price(p) - return dy[0] + return dy @external @@ -1401,7 +1413,14 @@ def get_D_mem( @view @internal -def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: +def _calc_withdraw_one_coin( + _burn_amount: uint256, + i: int128 +) -> ( + uint256, + uint256, + DynArray[uint256, MAX_COINS] +): # First, need to calculate # * Get current D # * Solve Eqn against y_i for D - _token_amount @@ -1435,17 +1454,15 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256[3]: dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors xp[i] = new_y - last_p: uint256 = 0 + last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) if new_y > 0: last_p = self._get_p(xp, amp, D1) - return [dy, dy_0 - dy, last_p] + return dy, dy_0 - dy, last_p # -------------------------- AMM Price Methods ------------------------------- -# TODO: fix this for dynamic N-COINS. The following only works for 2-coin pools. - @pure @internal def pack_prices(p1: uint256, p2: uint256) -> uint256: # <---- TODO: how to pack 8 numbers? @@ -1459,8 +1476,9 @@ def pack_prices(p1: uint256, p2: uint256) -> uint256: # <---- TODO: how to pack def _get_p( xp: DynArray[uint256, MAX_COINS], amp: uint256, - D: uint256 -) -> uint256: + D: uint256, +) -> DynArray[uint256, MAX_COINS]: + # dx_0 / dx_1 only, however can have any number of coins in pool ANN: uint256 = amp * N_COINS Dr: uint256 = D / pow_mod256(N_COINS, N_COINS) @@ -1472,19 +1490,37 @@ def _get_p( Dr = Dr * D / xp[i] - # TODO: the following does not work for N_COINS > 2; needs i and j to substitute 0 and 1: - return 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[1]) / (ANN * xp[0] / A_PRECISION + Dr) + p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(1, MAX_COINS): + if i == N_COINS: + break + p[i-1] = 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[i]) / (ANN * xp[0] / A_PRECISION + Dr) + + return p @internal -def save_p_from_price(last_price: uint256): # <---- TODO: this should accept last_prices with length MAX_COINS-1 +def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): """ Saves current price and its EMA """ - if last_price != 0: - self.last_prices_packed = self.pack_prices(last_price, self._ma_price()) - if self.ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + ma_last_time: uint256 = self.ma_last_time + + for i in range(MAX_COINS): + if i == N_COINS: + break + + if last_prices[i] != 0: + + # Upate packed prices ----------------- + self.last_prices_packed[i] = self.pack_prices(last_prices[i], self._ma_price(i)) + + # Update ma_last_time ------------------ + if ma_last_time < block.timestamp: + ma_last_time = block.timestamp + + self.ma_last_time = ma_last_time @internal @@ -1497,10 +1533,10 @@ def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): @internal @view -def _ma_price() -> uint256: # <---- TODO: this one needs an index! +def _ma_price(i: uint256) -> uint256: ma_last_time: uint256 = self.ma_last_time - pp: uint256 = self.last_prices_packed + pp: uint256 = self.last_prices_packed[i] last_price: uint256 = pp & (2**128 - 1) last_ema_price: uint256 = (pp >> 128) @@ -1514,32 +1550,38 @@ def _ma_price() -> uint256: # <---- TODO: this one needs an index! @view @external -def last_price() -> uint256: # <---- TODO: this one needs an index! - return self.last_prices_packed & (2**128 - 1) +def last_price(i: uint256) -> uint256: + return self.last_prices_packed[i] & (2**128 - 1) @view @external -def ema_price() -> uint256: # <---- TODO: this one needs an index! - return (self.last_prices_packed >> 128) +def ema_price(i: uint256) -> uint256: + return (self.last_prices_packed[i] >> 128) @external @view -def get_p() -> uint256: # <---- TODO: this one needs an index! +def get_p(i: uint256) -> uint256: + """ + @notice Returns the AMM State price of token + @dev if i = 0, it will return the state price of coin[1]. + @param i index of state price (0 for coin[1], 1 for coin[2], ...) + @return uint256 The state price quoted by the AMM for coin[i+1] + """ amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() ) D: uint256 = self.get_D(xp, amp) - return self._get_p(xp, amp, D) + return self._get_p(xp, amp, D)[i] @external @view @nonreentrant('lock') -def price_oracle() -> uint256: # <---- TODO: this one needs an index! - return self._ma_price() # <---- TODO: this one needs an index! +def price_oracle(i: uint256) -> uint256: + return self._ma_price(i) # ----------------------------- Math Utils ----------------------------------- From 483d5cb01fdd2fa64fa0f6b2d49e89329469a6c8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:40:42 +0200 Subject: [PATCH 085/337] add exchange_underlying methods --- contracts/main/CurveStableSwapNG.vy | 152 ++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 20 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 46c8b2cc..710a7729 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -670,6 +670,112 @@ def exchange_received( ) +@external +@nonreentrant('lock') +def exchange_underlying( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + For MetaNG: native token wrapping/unwrapping is disabled; this + parameter can be whatever. + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + empty(address), + empty(bytes32), + False + ) + + +@external +@nonreentrant('lock') +def exchange_underlying_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: no callback specified + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + msg.sender, + _cb, + False + ) + + +@external +@nonreentrant('lock') +def exchange_underlying_received( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _receiver: address, +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + empty(address), + empty(bytes32), + True + ) + + @payable @external @nonreentrant('lock') @@ -757,7 +863,7 @@ def add_liquidity( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D2: uint256 = self.get_D(xp, amp) mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) # TODO: this needs to be for Dynamic N_COINS + self.save_p(xp, amp, D2) else: @@ -868,7 +974,7 @@ def remove_liquidity_imbalance( D2: uint256 = self.get_D_mem(rates, new_balances, amp) - self.save_p(new_balances, amp, D2) # TODO: this needs to be for Dynamic N_COINS + self.save_p(new_balances, amp, D2) total_supply: uint256 = self.totalSupply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 @@ -944,17 +1050,17 @@ def withdraw_admin_fees(): def __exchange( dx: uint256, x: uint256, - xp: DynArray[uint256, MAX_COINS], + _xp: DynArray[uint256, MAX_COINS], rates: DynArray[uint256, MAX_COINS], i: int128, j: int128, ) -> uint256: amp: uint256 = self._A() - D: uint256 = self.get_D(xp, amp) - y: uint256 = self.get_y(i, j, x, xp, amp, D) + D: uint256 = self.get_D(_xp, amp) + y: uint256 = self.get_y(i, j, x, _xp, amp, D) - dy: uint256 = xp[j] - y - 1 # -1 just in case there were some rounding errors + dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR # Convert all to real units @@ -964,8 +1070,12 @@ def __exchange( dy_fee * ADMIN_FEE / FEE_DENOMINATOR ) * PRECISION / rates[j] + # Calculate and store state prices: + xp: DynArray[uint256, MAX_COINS] = _xp + xp[i] = x + xp[j] = y # D is not changed because we did not apply a fee - self.save_p([x, y], amp, D) # TODO: this needs to be for Dynamic N_COINS + self.save_p(xp, amp, D) return dy @@ -1070,6 +1180,7 @@ def _exchange_underlying( dx_w_fee: uint256 = 0 + # for exchange_underlying, optimistic transfers need to be handled differently if expect_optimistic_transfer: assert self.is_rebasing[input_coin] # dev: rebasing coins not supported @@ -1084,21 +1195,22 @@ def _exchange_underlying( assert dx_w_fee == _dx self.stored_balances[meta_i] += dx_w_fee - elif callback_sig != empty(bytes32): - - raw_call( - callbacker, - concat( - slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, input_coin, _dx, _min_dy) - ) - ) + dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx else: - assert ERC20(input_coin).transferFrom(sender, self, _dx, default_return_value=True) - - dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx + dx_w_fee = self._transfer_in( + i, + _dx, + _min_dy, + 0, # msg.value is always 0 for exchange_underlying + callbacker, + callback_sig, + sender, + receiver, + False, # use_eth = False + False, # expect_optimistic_transfer = False + ) # ------------------------------------------------------------------------ @@ -1465,7 +1577,7 @@ def _calc_withdraw_one_coin( @pure @internal -def pack_prices(p1: uint256, p2: uint256) -> uint256: # <---- TODO: how to pack 8 numbers? +def pack_prices(p1: uint256, p2: uint256) -> uint256: assert p1 < 2**128 assert p2 < 2**128 return p1 | (p2 << 128) From b8b10a83e75e6184c6800f692d384f621afd481c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:59:39 +0200 Subject: [PATCH 086/337] fix: max metapool coin index was 2, and should be 1 --- contracts/main/CurveStableSwapNG.vy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 710a7729..86f945a5 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -147,7 +147,7 @@ event ApplyNewFee: MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 -MAX_METAPOOL_COINS_128: constant(int128) = 2 +MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 # ---------------------------- Pool Variables -------------------------------- @@ -1166,13 +1166,13 @@ def _exchange_underlying( if i == 0: input_coin = coins[0] else: - base_i = i - MAX_METAPOOL_COINS_128 # if i == 1, this reverts + base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts meta_i = 1 input_coin = BASE_COINS[base_i] if j == 0: output_coin = coins[0] else: - base_j = j - MAX_METAPOOL_COINS_128 # if j == 1, this reverts + base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts meta_j = 1 output_coin = BASE_COINS[base_j] @@ -1223,8 +1223,8 @@ def _exchange_underlying( else: dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_METAPOOL_COINS_128] / PRECISION - x += xp[MAX_METAPOOL_COINS_128] + x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + x += xp[MAX_METAPOOL_COIN_INDEX] dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) @@ -1259,7 +1259,7 @@ def _exchange_underlying( @internal def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - coin_i: address = coins[MAX_METAPOOL_COINS_128] + coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] x: uint256 = ERC20(coin_i).balanceOf(self) if BASE_N_COINS == 2: From 26fcca4f9178204c1dc279c0b1d30513ac8fae45 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:30:56 +0200 Subject: [PATCH 087/337] exceeds eip170 --- README.MD | 5 ++--- contracts/main/CurveStableSwapNG.vy | 12 ++++-------- tests/fixtures/factory.py | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/README.MD b/README.MD index f7200243..2da08b64 100644 --- a/README.MD +++ b/README.MD @@ -6,8 +6,8 @@ Permissionless deployment of Curve metapools. The metapool factory has several core components: -* [`Factory`](contracts/main/CurveStableSwapFactoryNG.vy) is the main contract used to deploy new metapools. It also acts a registry for finding the deployed pools and querying information about them. -* New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwap2NG.vy) targetted by the proxy is determined according to the base pool. +- [`Factory`](contracts/main/CurveStableSwapFactoryNG.vy) is the main contract used to deploy new metapools. It also acts a registry for finding the deployed pools and querying information about them. +- New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwapNG.vy) targetted by the proxy is determined according to the base pool. See the [documentation](https://curve.readthedocs.io) for more detailed information. @@ -30,7 +30,6 @@ poetry install - `--decimals` - token decimals (divided by comma), default `18,18` - `--return-types` - types of .transfer() returns to test against (divided by comma), default `revert,False,None` - ### Type of tests Testing gauge diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 86f945a5..dce80456 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -677,7 +677,6 @@ def exchange_underlying( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -713,7 +712,6 @@ def exchange_underlying_extended( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, _cb: bytes32 ) -> uint256: @@ -749,7 +747,6 @@ def exchange_underlying_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, ) -> uint256: """ @@ -1607,7 +1604,8 @@ def _get_p( for i in range(1, MAX_COINS): if i == N_COINS: break - p[i-1] = 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[i]) / (ANN * xp[0] / A_PRECISION + Dr) + + p[i - 1] = 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[i]) / (ANN * xp[0] / A_PRECISION + Dr) return p @@ -1620,7 +1618,7 @@ def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): ma_last_time: uint256 = self.ma_last_time for i in range(MAX_COINS): - if i == N_COINS: + if i == N_COINS - 1: break if last_prices[i] != 0: @@ -2030,9 +2028,7 @@ def get_balances() -> DynArray[uint256, MAX_COINS]: @view @external def oracle(_idx: uint256) -> address: - if _idx < N_COINS: - return convert(self.oracles[_idx] % 2**160, address) - return empty(address) + return convert(self.oracles[_idx] % 2**160, address) @view diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 6435a7dd..27086eef 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -15,7 +15,7 @@ def gauge_implementation(deployer, gauge_interface): @pytest.fixture(scope="module") def amm_interface(): - return boa.load_partial("contracts/main/CurveStableSwap2NG.vy") + return boa.load_partial("contracts/main/CurveStableSwapNG.vy") @pytest.fixture(scope="module") From cd74b305131cc1a51ec9afa080d5d45afe8e65f2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 11 Jul 2023 18:22:05 +0200 Subject: [PATCH 088/337] accept defeat and split --- contracts/main/CurveStableSwapFactoryNG.vy | 2 - contracts/main/CurveStableSwapMetaNG.vy | 1918 ++++++++++++++++++++ contracts/main/CurveStableSwapNG.vy | 398 +--- 3 files changed, 1966 insertions(+), 352 deletions(-) create mode 100644 contracts/main/CurveStableSwapMetaNG.vy diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index d3457b9f..f09077af 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -538,9 +538,7 @@ def deploy_plain_pool( _fee, # _fee: uint256 _ma_exp_time, # _ma_exp_time: uint256 WETH20, # _weth: address - empty(address), # _base_pool: address _coins, # _coins: DynArray[address, MAX_COINS] - empty(DynArray[address, MAX_COINS]), # base_coins: DynArray[address, MAX_COINS] _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] _oracles, # _oracles: DynArray[address, MAX_COINS] diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy new file mode 100644 index 00000000..469fb789 --- /dev/null +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -0,0 +1,1918 @@ +# @version 0.3.9 +""" +@title CurveStableSwapNG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Stableswap implementation for up to 8 coins with no rehypothecation, + i.e. tokens are not deposited into other contracts. Supports only + token pairs that are similarly priced. This contract also + supports metapools (2-coin pools where the second coin is an LP token). + The Pool contract also records exponential moving averages for coins + 1, 2 and 3 relative to coin 0. +@dev Supports: + 1. ERC20 support for return True/revert, return True/False, return None + 2. ERC20 tokens can have arbitrary decimals (<=18). + 3. ERC20 tokens that rebase (either positive or fee on transfer) + 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) + Additional features include: + 1. Support for rebasing tokens: but this disables + `exchange_received`. + 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) + Note: Oracle precision _must_ be 10**18. + 3. Support for ETH/WETH transfers + 4. Adds oracles based on AMM State Price (and _not_ last traded price). + 5. Adds exchanging tokens with callbacks that allows for: + a. reduced ERC20 token transfers in zap contracts + b. swaps without transferFrom (no need for token approvals) + 6. Adds feature: `exchange_received`, which is inspired + by Uniswap V2: swaps that expect an ERC20 transfer to have occurred + prior to executing the swap. + Note: a. If pool contains rebasing tokens and `IS_REBASING` is True + then calling `exchange_received` will REVERT. + b. If pool contains rebasing token and `IS_REBASING` is False + then this is an incorrect implementation and rebases can be + stolen. + 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output + of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected + input of coin[i] for an output amount of coin[j]. +""" + +from vyper.interfaces import ERC20 + + +# ------------------------------- Interfaces --------------------------------- + +interface Factory: + def get_fee_receiver() -> address: view + def admin() -> address: view + def views_implementation() -> address: view + +interface WETH: + def deposit(): payable + def withdraw(_amount: uint256): nonpayable + +interface CurveLPToken: + def totalSupply() -> uint256: view + def mint(_to: address, _value: uint256) -> bool: nonpayable + def burnFrom(_to: address, _value: uint256) -> bool: nonpayable + +interface StableSwapViews: + def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool, + _pool: address + ) -> uint256: view + +interface StableSwap2: + def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable + +interface StableSwap3: + def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable + +interface StableSwap4: + def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable + +interface StableSwap: + def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable + def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable + def get_virtual_price() -> uint256: view + +# --------------------------------- Events ----------------------------------- + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + +event TokenExchange: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event TokenExchangeUnderlying: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event AddLiquidity: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + invariant: uint256 + token_supply: uint256 + +event RemoveLiquidity: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + token_supply: uint256 + +event RemoveLiquidityOne: + provider: indexed(address) + token_id: int128 + token_amount: uint256 + coin_amount: uint256 + token_supply: uint256 + +event RemoveLiquidityImbalance: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + invariant: uint256 + token_supply: uint256 + +event RampA: + old_A: uint256 + new_A: uint256 + initial_time: uint256 + future_time: uint256 + +event StopRampA: + A: uint256 + t: uint256 + +event ApplyNewFee: + fee: uint256 + + +MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory +MAX_COINS_128: constant(int128) = 8 +MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 + +# ---------------------------- Pool Variables -------------------------------- + +WETH20: immutable(address) +N_COINS: public(immutable(uint256)) +N_COINS_128: immutable(int128) +PRECISION: constant(uint256) = 10 ** 18 +lp_token: public(immutable(CurveLPToken)) + +# To denote that it is a plain pool: +BASE_POOL: public(immutable(address)) +BASE_N_COINS: public(immutable(uint256)) +BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) + +factory: public(immutable(Factory)) +coins: public(immutable(DynArray[address, MAX_COINS])) +stored_balances: DynArray[uint256, MAX_COINS] +fee: public(uint256) # fee * 1e10 +is_rebasing: HashMap[address, bool] + +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 + +# ---------------------- Pool Amplification Parameters ----------------------- + +A_PRECISION: constant(uint256) = 100 +MAX_A: constant(uint256) = 10 ** 6 +MAX_A_CHANGE: constant(uint256) = 10 + +initial_A: public(uint256) +future_A: public(uint256) +initial_A_time: public(uint256) +future_A_time: public(uint256) + +# ---------------------------- Admin Variables ------------------------------- + +ADMIN_FEE: constant(uint256) = 5000000000 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 +MIN_RAMP_TIME: constant(uint256) = 86400 +admin_balances: public(DynArray[uint256, MAX_COINS]) + +# ----------------------- Oracle Specific vars ------------------------------- + +rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) +# [bytes4 method_id][bytes8 ][bytes20 oracle] +oracles: DynArray[uint256, MAX_COINS] + +last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price +ma_exp_time: public(uint256) +ma_last_time: public(uint256) + +# shift(2**32 - 1, 224) +ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 + + +# ------------------------------ AMM Setup ----------------------------------- + + +@external +def __init__( + _name: String[32], + _symbol: String[10], + _A: uint256, + _fee: uint256, + _ma_exp_time: uint256, + _weth: address, + _base_pool: address, + _lp_token_implementation: address, + _coins: DynArray[address, MAX_COINS], + _base_coins: DynArray[address, MAX_COINS], + _rate_multipliers: DynArray[uint256, MAX_COINS], + _method_ids: DynArray[bytes4, MAX_COINS], + _oracles: DynArray[address, MAX_COINS], + _is_rebasing: DynArray[bool, MAX_COINS], +): + """ + @notice Initialize the pool contract + @param _name Name of the new plain pool. + @param _symbol Symbol for the new plain pool. + @param _coins List of addresses of the coins being used in the pool. + @param _A Amplification co-efficient - a lower value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + @param _is_rebasing Array of booleans where _is_rebasing[i] is True if _coins[i] is + a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. + """ + + WETH20 = _weth + BASE_POOL = _base_pool + BASE_COINS = _base_coins + coins = _coins + + __n_coins: uint256 = len(_coins) + __base_n_coins: uint256 = len(_base_coins) + BASE_N_COINS = __base_n_coins + + # ----------------- Parameters dependent of pool type -------------------- + + __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + if BASE_POOL != empty(address): + + assert __n_coins == 2 # dev: metapools can only have 2 coins + + self.is_rebasing[_coins[0]] = _is_rebasing[0] + # self.is_rebasing[_coins[1]] = False is ignored, + # because _coins[1] is base_lp_token and that does not rebase. + + for i in range(MAX_COINS): + if i < __base_n_coins: + self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] + + # Approval needed for add_liquidity operation on base pool in _exchange_underlying + ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) + + if i < __n_coins: + self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) # <--- TODO: check this! + + # Only need rate_multiplier for coin paired against basepool's lp token: + __rate_multipliers = [_rate_multipliers[0]] + + else: + + __n_coins = len(_coins) + for i in range(MAX_COINS): + if i == __n_coins: + break + self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) + __rate_multipliers = _rate_multipliers + + N_COINS = __n_coins + N_COINS_128 = convert(__n_coins, int128) + rate_multipliers = __rate_multipliers + + # ----------------- Parameters independent of pool type ------------------ + + factory = Factory(msg.sender) + + A: uint256 = _A * A_PRECISION + self.initial_A = A + self.future_A = A + self.fee = _fee + + assert _ma_exp_time != 0 + self.ma_exp_time = _ma_exp_time + self.ma_last_time = block.timestamp + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + # Enforce native token as coin[0] + if _coins[i] == WETH20: + assert i == 0, "ETH must be at index 0" + + self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? + + # --------------------------- Create LP Token ---------------------------- + + lp_token = CurveLPToken( + create_from_blueprint(_lp_token_implementation, _name, _symbol) + ) + + +# ------------------ Token transfers in and out of the AMM ------------------- + + +@payable +@external +def __default__(): + if msg.value > 0: + assert WETH20 in coins + + +@internal +def _transfer_in( + coin_idx: int128, + dx: uint256, + dy: uint256, + mvalue: uint256, + callbacker: address, + callback_sig: bytes32, + sender: address, + receiver: address, + use_eth: bool, + expect_optimistic_transfer: bool, +) -> uint256: + """ + @notice Contains all logic to handle ERC20 or native token transfers + @dev The callback sig must have the following args: + sender: address + receiver: address + coin: address + dx: uint256 + dy: uint256 + The `dy` that the pool enforces is actually min_dy. + Callback only occurs for `exchange_extended`. + Callback cannot happen for `_use_eth` = True. + @dev If callback_sig is empty, `_transfer_in` does a transferFrom. + @params _coin address of the coin to transfer in. + @params dx amount of `_coin` to transfer into the pool. + @params dy amount of `_coin` to transfer out of the pool. + @params mvalue msg.value if the transfer is ETH, 0 otherwise. + @params callbacker address to call `callback_sig` on. + @params callback_sig signature of the callback function. + @params sender address to transfer `_coin` from. + @params receiver address to transfer `_coin` to. + @params use_eth True if the transfer is ETH, False otherwise. + @params expect_optimistic_transfer True if contract expects an optimistic coin transfer + """ + _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) + incoming_coin_is_rebasing: bool = self.is_rebasing[coins[coin_idx]] + + # ------------------------- Handle Transfers ----------------------------- + + if use_eth and coins[coin_idx] == WETH20: + + _dx = mvalue + WETH(WETH20).deposit(value=dx) + + elif expect_optimistic_transfer: + + assert not incoming_coin_is_rebasing, "exchange_received not allowed if incoming token is rebasing" + _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] + + elif callback_sig != empty(bytes32): + + raw_call( + callbacker, + concat( + slice(callback_sig, 0, 4), + _abi_encode(sender, receiver, coins[coin_idx], dx, dy) + ) + ) + + _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + + else: + + assert ERC20(coins[coin_idx]).transferFrom( + sender, self, dx, default_return_value=True + ) + + _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + + # --------------------------- Check Transfer ----------------------------- + + if incoming_coin_is_rebasing: + assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! + else: + assert dx == _dx, "Pool did not receive tokens for swap" + + # ----------------------- Update Stored Balances ------------------------- + + self.stored_balances[coin_idx] += _dx + + return _dx + + +@internal +def _transfer_out( + _coin_idx: int128, _amount: uint256, use_eth: bool, receiver: address +): + """ + @notice Transfer a single token from the pool to receiver. + @dev This function is called by `remove_liquidity` and + `remove_liquidity_one` and `_exchange` methods. + @params _coin Address of the token to transfer out + @params _amount Amount of token to transfer out + @params use_eth Whether to transfer ETH or not + @params receiver Address to send the tokens to + """ + + # ------------------------- Handle Transfers ----------------------------- + + if use_eth and coins[_coin_idx] == WETH20: + + WETH(WETH20).withdraw(_amount) + raw_call(receiver, b"", value=_amount) + + else: + + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + + # ----------------------- Update Stored Balances ------------------------- + + self.stored_balances[_coin_idx] -= _amount + + +# -------------------------- AMM Special Methods ----------------------------- + + +@view +@internal +def _stored_rates() -> DynArray[uint256, MAX_COINS]: + """ + @notice Gets rate multipliers for each coin. + @dev If the coin has a rate oracle that has been properly initialised, + this method queries that rate by static-calling an external + contract. + """ + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + if BASE_POOL != empty(address): + rates = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] + else: + rates = rate_multipliers + + oracles: DynArray[uint256, MAX_COINS] = self.oracles + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if oracles[i] == 0: + continue + + # NOTE: assumed that response is of precision 10**18 + response: Bytes[32] = raw_call( + convert(oracles[i] % 2**160, address), + _abi_encode(oracles[i] & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ) + + assert len(response) != 0 + rates[i] = rates[i] * convert(response, uint256) / PRECISION + + return rates + + +@view +@internal +def _balances() -> DynArray[uint256, MAX_COINS]: + """ + @notice Calculates the pool's balances _excluding_ the admin's balances. + @dev This method ensures LPs keep all rebases and admin only claims swap fees. + """ + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) + + return result + + +# -------------------------- AMM Main Functions ------------------------------ + + +@payable +@external +@nonreentrant('lock') +def exchange( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + Allows for native token swaps (e.g. ETH <> whatever) + If native token is not in coin list and msg.value > 0, swap will revert + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + return self._exchange( + msg.sender, + msg.value, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32), + False + ) + + +@external +@nonreentrant('lock') +def exchange_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool, + _sender: address, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two coins after a callback + @dev Index values can be found via the `coins` public getter method + Not payable (does not accept eth). Users of this method are dex aggregators, + arbitrageurs, or other users who do not wish to grant approvals to the contract. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: No callback specified + return self._exchange( + _sender, + 0, # mvalue is zero here + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + msg.sender, # <---------------------------- callbacker is msg.sender. + _cb, + False + ) + + +@external +@nonreentrant('lock') +def exchange_received( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins without transferring token in + @dev The contract swaps tokens based on a change in balance of coin[i]. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `exchange_received`. + The method is non-payable: does not accept native token. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + return self._exchange( + msg.sender, + 0, + i, + j, + _dx, + _min_dy, + _use_eth, + _receiver, + empty(address), + empty(bytes32), + True, # <--------------------------------------- swap optimistically. + ) + + +@external +@nonreentrant('lock') +def exchange_underlying( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + For MetaNG: native token wrapping/unwrapping is disabled; this + parameter can be whatever. + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + empty(address), + empty(bytes32), + False + ) + + +@external +@nonreentrant('lock') +def exchange_underlying_extended( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _receiver: address, + _cb: bytes32 +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + assert _cb != empty(bytes32) # dev: no callback specified + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + msg.sender, + _cb, + False + ) + + +@external +@nonreentrant('lock') +def exchange_underlying_received( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _receiver: address, +) -> uint256: + """ + @notice Perform an exchange between two underlying coins + @dev Even if _use_eth is in the abi, the method does not accept native token + @param i Index value for the underlying coin to send + @param j Index value of the underlying coin to receive + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @param _use_eth Use native token transfers (if pool has WETH20) + @param _receiver Address that receives `j` + @return Actual amount of `j` received + """ + return self._exchange_underlying( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + empty(address), + empty(bytes32), + True + ) + + +@payable +@external +@nonreentrant('lock') +def add_liquidity( + _amounts: DynArray[uint256, MAX_COINS], + _min_mint_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + amp: uint256 = self._A() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = lp_token.totalSupply() + new_balances: DynArray[uint256, MAX_COINS] = old_balances + + # -------------------------- Do Transfers In ----------------------------- + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if _amounts[i] > 0: + + new_balances[i] += self._transfer_in( + i, + _amounts[i], + 0, + msg.value, + empty(address), + empty(bytes32), + msg.sender, + empty(address), + _use_eth, + False, # expect_optimistic_transfer + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) + D2: uint256 = self.get_D(xp, amp) + mint_amount = total_supply * (D2 - D0) / D0 + self.save_p(xp, amp, D2) + + else: + + mint_amount = D1 # Take the dust if there was any + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + lp_token.mint(msg.sender, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount + + +@external +@nonreentrant('lock') +def remove_liquidity_one_coin( + _burn_amount: uint256, + i: int128, + _min_received: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Withdraw a single coin from the pool + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_received Minimum amount of coin to receive + @param _receiver Address that receives the withdrawn coins + @return Amount of coin received + """ + dy: uint256 = 0 + fee: uint256 = 0 + p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) + assert dy >= _min_received, "Not enough coins removed" + + self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR + + lp_token.burnFrom(msg.sender, _burn_amount) + + log Transfer(msg.sender, empty(address), _burn_amount) + + self._transfer_out(i, dy, _use_eth, _receiver) + + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, lp_token.totalSupply()) + + self.save_p_from_price(p) + + return dy + + +@external +@nonreentrant('lock') +def remove_liquidity_imbalance( + _amounts: DynArray[uint256, MAX_COINS], + _max_burn_amount: uint256, + _use_eth: bool = False, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the withdrawn coins + @return Actual amount of the LP token burned in the withdrawal + """ + amp: uint256 = self._A() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + new_balances: DynArray[uint256, MAX_COINS] = old_balances + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if _amounts[i] != 0: + new_balances[i] -= _amounts[i] + self._transfer_out(i, _amounts[i], _use_eth, _receiver) + + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + + fees[i] = base_fee * difference / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + D2: uint256 = self.get_D_mem(rates, new_balances, amp) + + self.save_p(new_balances, amp, D2) + + total_supply: uint256 = lp_token.totalSupply() + burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 + assert burn_amount > 1 # dev: zero tokens burned + assert burn_amount <= _max_burn_amount, "Slippage screwed you" + + lp_token.burnFrom(msg.sender, burn_amount) + + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + + return burn_amount + + +@external +@nonreentrant('lock') +def remove_liquidity( + _burn_amount: uint256, + _min_amounts: DynArray[uint256, MAX_COINS], + _use_eth: bool = False, + _receiver: address = msg.sender, + _claim_admin_fees: bool = True, +) -> DynArray[uint256, MAX_COINS]: + """ + @notice Withdraw coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the withdrawn coins + @return List of amounts of coins that were withdrawn + """ + total_supply: uint256 = lp_token.totalSupply() + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = self._balances() + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + value: uint256 = balances[i] * _burn_amount / total_supply + assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" + amounts[i] = value + self._transfer_out(i, value, _use_eth, _receiver) + + lp_token.burnFrom(msg.sender, _burn_amount) # dev: insufficient funds + + log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # TODO: check this! + + # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. + if _claim_admin_fees: + self._withdraw_admin_fees() + + return amounts + + +@external +def withdraw_admin_fees(): + """ + @notice Claim admin fees. Callable by anyone. + """ + self._withdraw_admin_fees() + + +# ------------------------ AMM Internal Functions ---------------------------- + + +@internal +def __exchange( + dx: uint256, + x: uint256, + _xp: DynArray[uint256, MAX_COINS], + rates: DynArray[uint256, MAX_COINS], + i: int128, + j: int128, +) -> uint256: + + amp: uint256 = self._A() + D: uint256 = self.get_D(_xp, amp) + y: uint256 = self.get_y(i, j, x, _xp, amp, D) + + dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + + self.admin_balances[j] += ( + dy_fee * ADMIN_FEE / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # Calculate and store state prices: + xp: DynArray[uint256, MAX_COINS] = _xp + xp[i] = x + xp[j] = y + # D is not changed because we did not apply a fee + self.save_p(xp, amp, D) + + return dy + + +@internal +def _exchange( + sender: address, + mvalue: uint256, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + use_eth: bool, + receiver: address, + callbacker: address, + callback_sig: bytes32, + expect_optimistic_transfer: bool +) -> uint256: + + assert i != j # dev: coin index out of range + assert _dx > 0 # dev: do not exchange 0 coins + + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + + # --------------------------- Do Transfer in ----------------------------- + + # `dx` is whatever the pool received after ERC20 transfer: + dx: uint256 = self._transfer_in( + i, + _dx, + _min_dy, + mvalue, + callbacker, + callback_sig, + sender, + receiver, + use_eth, + expect_optimistic_transfer + ) + + # ------------------------------- Exchange ------------------------------- + + x: uint256 = xp[i] + dx * rates[i] / PRECISION + dy: uint256 = self.__exchange(dx, x, xp, rates, i, j) + assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" + + # --------------------------- Do Transfer out ---------------------------- + + self._transfer_out(j, dy, use_eth, receiver) + + # ------------------------------------------------------------------------ + + log TokenExchange(msg.sender, i, _dx, j, dy) + + return dy + + +@internal +def _exchange_underlying( + sender: address, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + receiver: address, + callbacker: address, + callback_sig: bytes32, + expect_optimistic_transfer: bool = False +) -> uint256: + + assert BASE_POOL != empty(address) # dev: pool is not a metapool + + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + input_coin: address = empty(address) + output_coin: address = empty(address) + + if i == 0: + input_coin = coins[0] + else: + base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts + meta_i = 1 + input_coin = BASE_COINS[base_i] + if j == 0: + output_coin = coins[0] + else: + base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts + meta_j = 1 + output_coin = BASE_COINS[base_j] + + # --------------------------- Do Transfer in ----------------------------- + + dx_w_fee: uint256 = 0 + + # for exchange_underlying, optimistic transfers need to be handled differently + if expect_optimistic_transfer: + + assert self.is_rebasing[input_coin] # dev: rebasing coins not supported + + # This branch is never reached for rebasing tokens + if input_coin == BASE_COINS[base_i]: + # we expect base_coin's balance to be 0. So swap whatever base_coin's + # balance the pool has: + dx_w_fee = ERC20(input_coin).balanceOf(self) + else: + dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] + assert dx_w_fee == _dx + self.stored_balances[meta_i] += dx_w_fee + + dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx + + else: + + dx_w_fee = self._transfer_in( + i, + _dx, + _min_dy, + 0, # msg.value is always 0 for exchange_underlying + callbacker, + callback_sig, + sender, + receiver, + False, # use_eth = False + False, # expect_optimistic_transfer = False + ) + + # ------------------------------------------------------------------------ + + if i == 0 or j == 0: # meta swap + + if i == 0: + + x = xp[i] + dx_w_fee * rates[i] / PRECISION + + else: + + dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) + x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + x += xp[MAX_METAPOOL_COIN_INDEX] + + dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) + + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount + + assert dy >= _min_dy + + # Adjust stored balances: + self.stored_balances[meta_j] -= dy + + else: # base pool swap (user should swap at base pool for better gas) + + dy = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy + + # --------------------------- Do Transfer out ---------------------------- + + assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) + + # ------------------------------------------------------------------------ + + log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! + + return dy + + +@internal +def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: + + coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] + x: uint256 = ERC20(coin_i).balanceOf(self) + + if BASE_N_COINS == 2: + + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) + + if BASE_N_COINS == 3: + + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) + + else: + + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) + + return ERC20(coin_i).balanceOf(self) - x + + +@internal +def _withdraw_admin_fees(): + + fee_receiver: address = factory.get_fee_receiver() + assert fee_receiver != empty(address) # dev: fee receiver not set + + admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if admin_balances[i] > 0: + + self._transfer_out(i, admin_balances[i], False, fee_receiver) + admin_balances[i] = 0 + + self.admin_balances = admin_balances + + +# --------------------------- AMM Math Functions ----------------------------- + + +@view +@internal +def get_y( + i: int128, + j: int128, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _D: uint256 +) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + amp: uint256 = _amp + D: uint256 = _D + if _D == 0: + amp = self._A() + D = self.get_D(xp, amp) + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + + if _i == i: + _x = x + elif _i != j: + _x = xp[_i] + else: + continue + + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@pure +@internal +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + D_P: uint256 = 0 + Dprev: uint256 = 0 + + for i in range(255): + D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + Dprev = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@pure +@internal +def get_y_D( + A: uint256, + i: int128, + xp: DynArray[uint256, MAX_COINS], + D: uint256 +) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + + if _i != i: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def _A() -> uint256: + """ + Handle ramping A up or down + """ + t1: uint256 = self.future_A_time + A1: uint256 = self.future_A + + if block.timestamp < t1: + A0: uint256 = self.initial_A + t0: uint256 = self.initial_A_time + # Expressions in uint256 cannot have negative numbers, thus "if" + if A1 > A0: + return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + else: + return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + + else: # when t1 == 0 or block.timestamp >= t1 + return A1 + + +@pure +@internal +def _xp_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS] +) -> DynArray[uint256, MAX_COINS]: + + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + result[i] = _rates[i] * _balances[i] / PRECISION + + return result + + +@view +@internal +def get_D_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS], + _amp: uint256 +) -> uint256: + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + +@view +@internal +def _calc_withdraw_one_coin( + _burn_amount: uint256, + i: int128 +) -> ( + uint256, + uint256, + DynArray[uint256, MAX_COINS] +): + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + amp: uint256 = self._A() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) + D0: uint256 = self.get_D(xp, amp) + + total_supply: uint256 = lp_token.totalSupply() + D1: uint256 = D0 - _burn_amount * D0 / total_supply + new_y: uint256 = self.get_y_D(amp, i, xp, D1) + + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for j in range(MAX_COINS_128): + + if j == N_COINS_128: + break + + dx_expected: uint256 = 0 + xp_j: uint256 = xp[j] + if j == i: + dx_expected = xp_j * D1 / D0 - new_y + else: + dx_expected = xp_j - xp_j * D1 / D0 + xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) + dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees + dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + + xp[i] = new_y + last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + if new_y > 0: + last_p = self._get_p(xp, amp, D1) + + return dy, dy_0 - dy, last_p + + +# -------------------------- AMM Price Methods ------------------------------- + +@pure +@internal +def pack_prices(p1: uint256, p2: uint256) -> uint256: + assert p1 < 2**128 + assert p2 < 2**128 + return p1 | (p2 << 128) + + +@internal +@view +def _get_p( + xp: DynArray[uint256, MAX_COINS], + amp: uint256, + D: uint256, +) -> DynArray[uint256, MAX_COINS]: + + # dx_0 / dx_1 only, however can have any number of coins in pool + ANN: uint256 = unsafe_mul(amp, N_COINS) + Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + Dr = Dr * D / xp[i] + + p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp0_A: uint256 = ANN * xp[0] / A_PRECISION + for i in range(1, MAX_COINS): + if i == N_COINS: + break + + p[i - 1] = 10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr) + + return p + + +@internal +def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): + """ + Saves current price and its EMA + """ + ma_last_time: uint256 = self.ma_last_time + + for i in range(MAX_COINS): + if i == N_COINS - 1: + break + + if last_prices[i] != 0: + + # Upate packed prices ----------------- + self.last_prices_packed[i] = self.pack_prices(last_prices[i], self._ma_price(i)) + + # Update ma_last_time ------------------ + if ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp + + +@internal +def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): + """ + Saves current price and its EMA + """ + self.save_p_from_price(self._get_p(xp, amp, D)) + + +@internal +@view +def _ma_price(i: uint256) -> uint256: + ma_last_time: uint256 = self.ma_last_time + + pp: uint256 = self.last_prices_packed[i] + last_price: uint256 = pp & (2**128 - 1) + last_ema_price: uint256 = (pp >> 128) + + if ma_last_time < block.timestamp: + alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) + return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 + + else: + return last_ema_price + + +@view +@external +def last_price(i: uint256) -> uint256: + return self.last_prices_packed[i] & (2**128 - 1) + + +@view +@external +def ema_price(i: uint256) -> uint256: + return (self.last_prices_packed[i] >> 128) + + +@external +@view +def get_p(i: uint256) -> uint256: + """ + @notice Returns the AMM State price of token + @dev if i = 0, it will return the state price of coin[1]. + @param i index of state price (0 for coin[1], 1 for coin[2], ...) + @return uint256 The state price quoted by the AMM for coin[i+1] + """ + amp: uint256 = self._A() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) + D: uint256 = self.get_D(xp, amp) + return self._get_p(xp, amp, D)[i] + + +@external +@view +@nonreentrant('lock') +def price_oracle(i: uint256) -> uint256: + return self._ma_price(i) + + +# ----------------------------- Math Utils ----------------------------------- + + +@internal +@pure +def exp(x: int256) -> uint256: + + """ + @dev Calculates the natural exponential function of a signed integer with + a precision of 1e18. + @notice Note that this function consumes about 810 gas units. The implementation + is inspired by Remco Bloemen's implementation under the MIT license here: + https://xn--2-umb.com/22/exp-ln. + @dev This implementation is derived from Snekmate, which is authored + by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. + https://github.com/pcaversaccio/snekmate + @param x The 32-byte variable. + @return int256 The 32-byte calculation result. + """ + value: int256 = x + + # If the result is `< 0.5`, we return zero. This happens when we have the following: + # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". + if (x <= -42139678854452767551): + return empty(uint256) + + # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. + # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". + assert x < 135305999368893231589, "wad_exp overflow" + + # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher + # intermediate precision and a binary base. This base conversion is a multiplication with + # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". + value = unsafe_div(x << 78, 5 ** 18) + + # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two + # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives + # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". + k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 + value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) + + # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, + # we will multiply by a scaling factor later. + y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) + p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ + 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) + + # We leave `p` in the "2 ** 192" base so that we do not have to scale it up + # again for the division. + q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) + q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) + q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) + + # The polynomial `q` has no zeros in the range because all its roots are complex. + # No scaling is required, as `p` is already "2 ** 96" too large. Also, + # `r` is in the range "(0.09, 0.25) * 2**96" after the division. + r: int256 = unsafe_div(p, q) + + # To finalise the calculation, we have to multiply `r` by: + # - the scale factor "s = ~6.031367120", + # - the factor "2 ** k" from the range reduction, and + # - the factor "1e18 / 2 ** 96" for the base conversion. + # We do this all at once, with an intermediate result in "2**213" base, + # so that the final right shift always gives a positive value. + + # Note that to circumvent Vyper's safecast feature for the potentially + # negative parameter value `r`, we first convert `r` to `bytes32` and + # subsequently to `uint256`. Remember that the EVM default behaviour is + # to use two's complement representation to handle signed integers. + return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) + + +# ------------------------- AMM View Functions ------------------------------- + + +@view +@external +def get_dx(i: int128, j: int128, dy: uint256) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ + return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) + + +@view +@external +def get_dy(i: int128, j: int128, dx: uint256) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) + + +@view +@external +def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing a single coin + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @return Amount of coin received + """ + return self._calc_withdraw_one_coin(_burn_amount, i)[0] + + +@view +@external +@nonreentrant('lock') +def get_virtual_price() -> uint256: + """ + @notice The current virtual price of the pool LP token + @dev Useful for calculating profits + @return LP token virtual price normalized to 1e18 + """ + amp: uint256 = self._A() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = self.get_D(xp, amp) + # D is in the units similar to DAI (e.g. converted to precision 1e18) + # When balanced, D = n * x_u - total virtual value of the portfolio + return D * PRECISION / lp_token.totalSupply() + + +@view +@external +def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool +) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + amounts[i] = _amounts[i] + + views: address = factory.views_implementation() + return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) + + +@view +@external +def admin_fee() -> uint256: + return ADMIN_FEE + + +@view +@external +def A() -> uint256: + return self._A() / A_PRECISION + + +@view +@external +def A_precise() -> uint256: + return self._A() + + +@view +@external +def balances(i: uint256) -> uint256: + """ + @notice Get the current balance of a coin within the + pool, less the accrued admin fees + @param i Index value for the coin to query balance of + @return Token balance + """ + return self._balances()[i] + + +@view +@external +def get_balances() -> DynArray[uint256, MAX_COINS]: + return self._balances() + + +@view +@external +def oracle(_idx: uint256) -> address: + return convert(self.oracles[_idx] % 2**160, address) + + +@view +@external +def stored_rates(i: uint256) -> uint256: + return self._stored_rates()[i] + + +# --------------------------- AMM Admin Functions ---------------------------- + + +@external +def ramp_A(_future_A: uint256, _future_time: uint256): + assert msg.sender == factory.admin() # dev: only owner + assert block.timestamp >= self.initial_A_time + MIN_RAMP_TIME + assert _future_time >= block.timestamp + MIN_RAMP_TIME # dev: insufficient time + + _initial_A: uint256 = self._A() + _future_A_p: uint256 = _future_A * A_PRECISION + + assert _future_A > 0 and _future_A < MAX_A + if _future_A_p < _initial_A: + assert _future_A_p * MAX_A_CHANGE >= _initial_A + else: + assert _future_A_p <= _initial_A * MAX_A_CHANGE + + self.initial_A = _initial_A + self.future_A = _future_A_p + self.initial_A_time = block.timestamp + self.future_A_time = _future_time + + log RampA(_initial_A, _future_A_p, block.timestamp, _future_time) + + +@external +def stop_ramp_A(): + assert msg.sender == factory.admin() # dev: only owner + + current_A: uint256 = self._A() + self.initial_A = current_A + self.future_A = current_A + self.initial_A_time = block.timestamp + self.future_A_time = block.timestamp + # now (block.timestamp < t1) is always False, so we return saved A + + log StopRampA(current_A, block.timestamp) + + +@external +def apply_new_fee(_new_fee: uint256): + + assert msg.sender == factory.admin() + assert _new_fee <= MAX_FEE + self.fee = _new_fee + + log ApplyNewFee(_new_fee) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256): + """ + @notice Set the moving average window of the price oracle. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == factory.admin() # dev: only owner + assert _ma_exp_time != 0 + + self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index dce80456..8fd93832 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -48,13 +48,13 @@ interface Factory: def admin() -> address: view def views_implementation() -> address: view -interface ERC1271: - def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view - interface WETH: def deposit(): payable def withdraw(_amount: uint256): nonpayable +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view @@ -64,20 +64,6 @@ interface StableSwapViews: _pool: address ) -> uint256: view -interface StableSwap2: - def add_liquidity(amounts: uint256[2], min_mint_amount: uint256): nonpayable - -interface StableSwap3: - def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable - -interface StableSwap4: - def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable - -interface StableSwap: - def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable - def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable - def get_virtual_price() -> uint256: view - # --------------------------------- Events ----------------------------------- event Transfer: @@ -149,6 +135,9 @@ MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 +# Implementation does not impose transfer restrictions: +PERMISSIONED: public(constant(bool)) = False + # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) @@ -156,13 +145,8 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -# Implementation does not impose transfer restrictions: -PERMISSIONED: public(constant(bool)) = False - # To denote that it is a plain pool: -BASE_POOL: public(immutable(address)) -BASE_N_COINS: public(immutable(uint256)) -BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) +BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) @@ -196,14 +180,14 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: DynArray[uint256, MAX_COINS] # [last_price, ma_price] +last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price ma_exp_time: public(uint256) ma_last_time: public(uint256) # shift(2**32 - 1, 224) ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 -# ----------------------- ERC20 Specific vars -------------------------------- +# --------------------------- ERC20 Specific Vars ---------------------------- name: public(immutable(String[64])) symbol: public(immutable(String[32])) @@ -238,9 +222,7 @@ def __init__( _fee: uint256, _ma_exp_time: uint256, _weth: address, - _base_pool: address, _coins: DynArray[address, MAX_COINS], - _base_coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], _method_ids: DynArray[bytes4, MAX_COINS], _oracles: DynArray[address, MAX_COINS], @@ -271,56 +253,20 @@ def __init__( """ WETH20 = _weth - BASE_POOL = _base_pool - BASE_COINS = _base_coins coins = _coins - __n_coins: uint256 = len(_coins) - __base_n_coins: uint256 = len(_base_coins) - BASE_N_COINS = __base_n_coins - - # ----------------- Parameters dependent of pool type -------------------- + N_COINS = __n_coins + N_COINS_128 = convert(__n_coins, int128) __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(MAX_COINS): + if i == __n_coins: + break + self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) + __rate_multipliers = _rate_multipliers - if BASE_POOL != empty(address): - - assert __n_coins == 2 # dev: metapools can only have 2 coins - - self.is_rebasing[_coins[0]] = _is_rebasing[0] - # self.is_rebasing[_coins[1]] = False is ignored, - # because _coins[1] is base_lp_token and that does not rebase. - - for i in range(MAX_COINS): - if i < __base_n_coins: - self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] - - # Approval needed for add_liquidity operation on base pool in _exchange_underlying - ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - - if i < __n_coins: - self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) # <--- TODO: check this! - - # Only need rate_multiplier for coin paired against basepool's lp token: - __rate_multipliers = [_rate_multipliers[0]] - - else: - - __n_coins = len(_coins) - for i in range(MAX_COINS): - if i == __n_coins: - break - self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) - __rate_multipliers = _rate_multipliers - - N_COINS = __n_coins - N_COINS_128 = convert(__n_coins, int128) rate_multipliers = __rate_multipliers - # ----------------- Parameters independent of pool type ------------------ - - name = _name - symbol = _symbol factory = Factory(msg.sender) A: uint256 = _A * A_PRECISION @@ -344,6 +290,11 @@ def __init__( self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? + # --------------------------- ERC20 stuff ---------------------------- + + name = _name + symbol = _symbol + # EIP712 related params ----------------- NAME_HASH = keccak256(name) salt = block.prevhash @@ -361,7 +312,7 @@ def __init__( # ------------------------ Fire a transfer event ------------------------- - log Transfer(empty(address), self, 0) + log Transfer(empty(address), msg.sender, 0) # ------------------ Token transfers in and out of the AMM ------------------- @@ -503,12 +454,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: this method queries that rate by static-calling an external contract. """ - rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - if BASE_POOL != empty(address): - rates = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] - else: - rates = rate_multipliers - + rates: DynArray[uint256, MAX_COINS] = rate_multipliers oracles: DynArray[uint256, MAX_COINS] = self.oracles for i in range(MAX_COINS_128): @@ -670,109 +616,6 @@ def exchange_received( ) -@external -@nonreentrant('lock') -def exchange_underlying( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - For MetaNG: native token wrapping/unwrapping is disabled; this - parameter can be whatever. - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_underlying_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: no callback specified - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, - _cb, - False - ) - - -@external -@nonreentrant('lock') -def exchange_underlying_received( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address, -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - empty(address), - empty(bytes32), - True - ) - - @payable @external @nonreentrant('lock') @@ -903,15 +746,12 @@ def remove_liquidity_one_coin( assert dy >= _min_received, "Not enough coins removed" self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR - total_supply: uint256 = self.totalSupply - _burn_amount - self.totalSupply = total_supply - self.balanceOf[msg.sender] -= _burn_amount - log Transfer(msg.sender, empty(address), _burn_amount) + self._burnFrom(msg.sender, _burn_amount) self._transfer_out(i, dy, _use_eth, _receiver) - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, total_supply) + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.totalSupply) self.save_p_from_price(p) @@ -979,9 +819,8 @@ def remove_liquidity_imbalance( assert burn_amount <= _max_burn_amount, "Slippage screwed you" total_supply -= burn_amount - self.totalSupply = total_supply - self.balanceOf[msg.sender] -= burn_amount - log Transfer(msg.sender, empty(address), burn_amount) + self._burnFrom(msg.sender, burn_amount) + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) return burn_amount @@ -1019,11 +858,9 @@ def remove_liquidity( self._transfer_out(i, value, _use_eth, _receiver) total_supply -= _burn_amount - self.balanceOf[msg.sender] -= _burn_amount - self.totalSupply = total_supply - log Transfer(msg.sender, empty(address), _burn_amount) + self._burnFrom(msg.sender, _burn_amount) - log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) + log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # TODO: check this! # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. if _claim_admin_fees: @@ -1132,154 +969,6 @@ def _exchange( return dy -@internal -def _exchange_underlying( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - callbacker: address, - callback_sig: bytes32, - expect_optimistic_transfer: bool = False -) -> uint256: - - assert BASE_POOL != empty(address) # dev: pool is not a metapool - - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) - - dy: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - x: uint256 = 0 - input_coin: address = empty(address) - output_coin: address = empty(address) - - if i == 0: - input_coin = coins[0] - else: - base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts - meta_i = 1 - input_coin = BASE_COINS[base_i] - if j == 0: - output_coin = coins[0] - else: - base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts - meta_j = 1 - output_coin = BASE_COINS[base_j] - - # --------------------------- Do Transfer in ----------------------------- - - dx_w_fee: uint256 = 0 - - # for exchange_underlying, optimistic transfers need to be handled differently - if expect_optimistic_transfer: - - assert self.is_rebasing[input_coin] # dev: rebasing coins not supported - - # This branch is never reached for rebasing tokens - if input_coin == BASE_COINS[base_i]: - # we expect base_coin's balance to be 0. So swap whatever base_coin's - # balance the pool has: - dx_w_fee = ERC20(input_coin).balanceOf(self) - else: - dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] - assert dx_w_fee == _dx - self.stored_balances[meta_i] += dx_w_fee - - dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx - - else: - - dx_w_fee = self._transfer_in( - i, - _dx, - _min_dy, - 0, # msg.value is always 0 for exchange_underlying - callbacker, - callback_sig, - sender, - receiver, - False, # use_eth = False - False, # expect_optimistic_transfer = False - ) - - # ------------------------------------------------------------------------ - - if i == 0 or j == 0: # meta swap - - if i == 0: - - x = xp[i] + dx_w_fee * rates[i] / PRECISION - - else: - - dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION - x += xp[MAX_METAPOOL_COIN_INDEX] - - dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) - - # Withdraw from the base pool if needed - if j > 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= _min_dy - - # Adjust stored balances: - self.stored_balances[meta_j] -= dy - - else: # base pool swap (user should swap at base pool for better gas) - - dy = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # --------------------------- Do Transfer out ---------------------------- - - assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) - - # ------------------------------------------------------------------------ - - log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! - - return dy - - -@internal -def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - - coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] - x: uint256 = ERC20(coin_i).balanceOf(self) - - if BASE_N_COINS == 2: - - base_inputs: uint256[2] = empty(uint256[2]) - base_inputs[base_i] = dx - StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) - - if BASE_N_COINS == 3: - - base_inputs: uint256[3] = empty(uint256[3]) - base_inputs[base_i] = dx - StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) - - else: - - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) - - return ERC20(coin_i).balanceOf(self) - x - - @internal def _withdraw_admin_fees(): @@ -1395,9 +1084,12 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: D: uint256 = S Ann: uint256 = _amp * N_COINS + D_P: uint256 = 0 + Dprev: uint256 = 0 + for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) - Dprev: uint256 = D + D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + Dprev = D D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) # Equality with the precision of 1 if D > Dprev: @@ -1589,8 +1281,8 @@ def _get_p( ) -> DynArray[uint256, MAX_COINS]: # dx_0 / dx_1 only, however can have any number of coins in pool - ANN: uint256 = amp * N_COINS - Dr: uint256 = D / pow_mod256(N_COINS, N_COINS) + ANN: uint256 = unsafe_mul(amp, N_COINS) + Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) for i in range(MAX_COINS_128): @@ -1600,12 +1292,12 @@ def _get_p( Dr = Dr * D / xp[i] p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - + xp0_A: uint256 = ANN * xp[0] / A_PRECISION for i in range(1, MAX_COINS): if i == N_COINS: break - p[i - 1] = 10**18 * (ANN * xp[0] / A_PRECISION + Dr * xp[0] / xp[i]) / (ANN * xp[0] / A_PRECISION + Dr) + p[i - 1] = 10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr) return p @@ -1628,9 +1320,7 @@ def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): # Update ma_last_time ------------------ if ma_last_time < block.timestamp: - ma_last_time = block.timestamp - - self.ma_last_time = ma_last_time + self.ma_last_time = block.timestamp @internal @@ -1768,7 +1458,7 @@ def exp(x: int256) -> uint256: return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) -# --------------------------- ERC20 Functionality ---------------------------- +# ---------------------------- ERC20 Utils ----------------------------------- @view @internal @@ -1797,6 +1487,14 @@ def _transfer(_from: address, _to: address, _value: uint256): log Transfer(_from, _to, _value) +@internal +def _burnFrom(_from: address, _burn_amount: uint256): + + self.totalSupply -= _burn_amount + self.balanceOf[_from] -= _burn_amount + log Transfer(_from, empty(address), _burn_amount) + + @external def transfer(_to : address, _value : uint256) -> bool: """ From b12389522ecfaa20b303560f7339a7d033024746 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Tue, 11 Jul 2023 19:37:14 +0200 Subject: [PATCH 089/337] fix bug --- contracts/main/CurveStableSwapNG.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 8fd93832..ddc067ae 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -262,7 +262,7 @@ def __init__( for i in range(MAX_COINS): if i == __n_coins: break - self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) + self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) __rate_multipliers = _rate_multipliers rate_multipliers = __rate_multipliers From d3bcd76b993afb614cce71ec0218020132abe28d Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:29:56 +0200 Subject: [PATCH 090/337] replace is_rebasing with asset_types for plain --- contracts/main/CurveStableSwapFactoryNG.vy | 40 +++++++++------------- contracts/main/CurveStableSwapNG.vy | 35 +++++++++---------- tests/fixtures/pools.py | 32 ++++++----------- 3 files changed, 45 insertions(+), 62 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index f09077af..0254c988 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -18,7 +18,6 @@ struct PoolArray: struct BasePoolArray: lp_token: address coins: DynArray[address, MAX_COINS] - is_rebasing: DynArray[bool, MAX_COINS] decimals: uint256 n_coins: uint256 asset_types: DynArray[uint8, MAX_COINS] @@ -467,10 +466,9 @@ def deploy_plain_pool( _fee: uint256, _ma_exp_time: uint256, _implementation_idx: uint256 = 0, + _asset_types: DynArray[uint8, MAX_COINS] = empty(DynArray[uint8, MAX_COINS]), _method_ids: DynArray[bytes4, MAX_COINS] = empty(DynArray[bytes4, MAX_COINS]), _oracles: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]), - _asset_types: DynArray[uint8, MAX_COINS] = empty(DynArray[uint8, MAX_COINS]), - _is_rebasing: DynArray[bool, MAX_COINS] = empty(DynArray[bool, MAX_COINS]) ) -> address: """ @notice Deploy a new plain pool @@ -489,23 +487,19 @@ def deploy_plain_pool( 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _implementation_idx Index of the implementation to use + @param _asset_types Asset types for pool, as an integer + 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _asset_types Asset types for pool, as an integer - 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING - @param _implementation_idx Index of the implementation to use. All possible - implementations for a pool of N_COINS can be publicly accessed - via `plain_implementations(N_COINS)` - @param _is_rebasing If any of the coins rebases, then this should be set to True. @return Address of the deployed pool """ assert _fee <= 100000000, "Invalid fee" assert len(_coins) == len(_method_ids), "All coin arrays should be same length" assert len(_coins) == len(_oracles), "All coin arrays should be same length" assert len(_coins) == len(_asset_types), "All coin arrays should be same length" - assert len(_coins) == len(_is_rebasing), "All coin arrays should be same length" n_coins: uint256 = len(_coins) _rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -540,9 +534,9 @@ def deploy_plain_pool( WETH20, # _weth: address _coins, # _coins: DynArray[address, MAX_COINS] _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] + _asset_types, # _asset_types: DynArray[uint8, MAX_COINS] _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] _oracles, # _oracles: DynArray[address, MAX_COINS] - _is_rebasing, # _is_rebasing: DynArray[bool, MAX_COINS] code_offset=3 ) @@ -595,7 +589,6 @@ def deploy_metapool( _asset_type: uint8 = 0, _method_id: bytes4 = empty(bytes4), _oracle: address = empty(address), - _is_rebasing: bool = False ) -> address: """ @notice Deploy a new metapool @@ -617,7 +610,15 @@ def deploy_metapool( @param _implementation_idx Index of the implementation to use. All possible implementations for a BASE_POOL can be publicly accessed via `metapool_implementations(BASE_POOL)` - @param _is_rebasing If _coin rebases, then this should be set to True. + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _implementation_idx Index of the implementation to use + @param _asset_type Asset type for token, as an integer + 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING + @param _method_id First four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracle Rate oracle address. @return Address of the deployed pool """ assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" @@ -633,18 +634,13 @@ def deploy_metapool( decimals: uint256 = ERC20(_coin).decimals() assert decimals < 19, "Max 18 decimals for coins" - # combine _coins's _is_rebasing and basepool coins _is_rebasing: - base_pool_is_rebasing: DynArray[bool, MAX_COINS] = self.base_pool_data[_base_pool].is_rebasing - is_rebasing: DynArray[bool, MAX_COINS] = [_is_rebasing, False] - + # combine _coins's _asset_type and basepool coins _asset_types: base_pool_asset_types: DynArray[uint8, MAX_COINS] = self.base_pool_data[_base_pool].asset_types asset_types: DynArray[uint8, MAX_COINS] = [_asset_type, 0] for i in range(0, MAX_COINS): if i == base_pool_n_coins: break - - is_rebasing.append(base_pool_is_rebasing[i]) asset_types.append(base_pool_asset_types[i]) _coins: DynArray[address, MAX_COINS] = [_coin, self.base_pool_data[_base_pool].lp_token] @@ -652,6 +648,7 @@ def deploy_metapool( _method_ids: DynArray[bytes4, MAX_COINS] = [_method_id, empty(bytes4)] _oracles: DynArray[address, MAX_COINS] = [_oracle, empty(address)] + # TODO: fix meta pool: address = create_from_blueprint( implementation, _name, # _name: String[32] @@ -664,9 +661,9 @@ def deploy_metapool( _coins, # _coins: DynArray[address, MAX_COINS] self.base_pool_data[_base_pool].coins, # base_coins: DynArray[address, MAX_COINS] _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] + asset_types, # asset_types: DynArray[uint8, MAX_COINS] _method_ids, # _method_ids: DynArray[bytes4, MAX_COINS] _oracles, # _oracles: DynArray[address, MAX_COINS] - is_rebasing, # is_rebasing: DynArray[bool, MAX_COINS] code_offset=3 ) @@ -734,14 +731,12 @@ def add_base_pool( _coins: DynArray[address, MAX_COINS], _asset_types: DynArray[uint8, MAX_COINS], _n_coins: uint256, - _is_rebasing: DynArray[bool, MAX_COINS], ): """ @notice Add a base pool to the registry, which may be used in factory metapools @dev Only callable by admin @param _base_pool Pool address to add @param _asset_types Asset type for pool, as an integer 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing - @param _is_rebasing Array of booleans: _is_rebasing[i] is True if basepool coin[i] is rebasing """ assert msg.sender == self.admin # dev: admin-only function assert len(self.base_pool_data[_base_pool].coins) == 0 # dev: pool exists @@ -763,7 +758,6 @@ def add_base_pool( coin: address = coins[i] self.base_pool_data[_base_pool].coins.append(coin) self.base_pool_data[_base_pool].asset_types.append(_asset_types[i]) - self.base_pool_data[_base_pool].is_rebasing.append(_is_rebasing[i]) self.base_pool_assets[coin] = True decimals += (ERC20(coin).decimals() << i*8) self.base_pool_data[_base_pool].decimals = decimals diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index ddc067ae..25c53785 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -5,11 +5,15 @@ @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved @notice Stableswap implementation for up to 8 coins with no rehypothecation, i.e. tokens are not deposited into other contracts. Supports only - token pairs that are similarly priced. This contract also - supports metapools (2-coin pools where the second coin is an LP token). + token pairs that are similarly priced. The Pool contract also records exponential moving averages for coins 1, 2 and 3 relative to coin 0. -@dev Supports: +@dev Asset Types: + 0. Basic ERC20 token with no additional features + 1. WETH - can we directly converted to/from ETH + 2. Oracle - token with rate oracle + 3. Rebasing - token with rebase (e.g. stETH) + Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). 3. ERC20 tokens that rebase (either positive or fee on transfer) @@ -27,9 +31,9 @@ 6. Adds feature: `exchange_received`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. - Note: a. If pool contains rebasing tokens and `IS_REBASING` is True + Note: a. If pool contains rebasing tokens and one of the `asset_types` is 3 (Rebasing) then calling `exchange_received` will REVERT. - b. If pool contains rebasing token and `IS_REBASING` is False + b. If pool contains rebasing token and `asset_types` does not contain 3 (Rebasing) then this is an incorrect implementation and rebases can be stolen. 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output @@ -145,14 +149,11 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -# To denote that it is a plain pool: -BASE_POOL: public(constant(address)) = 0x0000000000000000000000000000000000000000 - factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -is_rebasing: HashMap[address, bool] +asset_types: public(DynArray[uint8, MAX_COINS]) FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -224,9 +225,9 @@ def __init__( _weth: address, _coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], + _asset_types: DynArray[uint8, MAX_COINS], _method_ids: DynArray[bytes4, MAX_COINS], _oracles: DynArray[address, MAX_COINS], - _is_rebasing: DynArray[bool, MAX_COINS], ): """ @notice Initialize the pool contract @@ -244,12 +245,11 @@ def __init__( 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _asset_types Array of uint8 representing tokens in pool @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _is_rebasing Array of booleans where _is_rebasing[i] is True if _coins[i] is - a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. """ WETH20 = _weth @@ -258,14 +258,13 @@ def __init__( N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) - __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS): if i == __n_coins: break self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) - __rate_multipliers = _rate_multipliers - rate_multipliers = __rate_multipliers + rate_multipliers = _rate_multipliers + self.asset_types = _asset_types factory = Factory(msg.sender) @@ -362,7 +361,7 @@ def _transfer_in( @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - incoming_coin_is_rebasing: bool = self.is_rebasing[coins[coin_idx]] + _incoming_coin_asset_type: uint8 = self.asset_types[coin_idx] # ------------------------- Handle Transfers ----------------------------- @@ -373,7 +372,7 @@ def _transfer_in( elif expect_optimistic_transfer: - assert not incoming_coin_is_rebasing, "exchange_received not allowed if incoming token is rebasing" + assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -398,7 +397,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if incoming_coin_is_rebasing: + if _incoming_coin_asset_type == 3: assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! else: assert dx == _dx, "Pool did not receive tokens for swap" diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 42c441b4..d37b9369 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -25,43 +25,33 @@ def swap( fee = 1000000 method_ids = [bytes(b"")] * pool_size oracles = [zero_address] * pool_size - asset_type = [] + asset_types = [] is_rebasing = [False] * pool_size for i, t in enumerate(pool_token_types): if t == 0: A = 2000 fee = 1000000 - asset_type.append(0) + asset_types.append(0) elif t == 1: A = 1000 fee = 3000000 - asset_type.append(1) + asset_types.append(1) elif t == 2: A = 1000 fee = 3000000 - asset_type.append(2) + asset_types.append(2) method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address elif t == 3: A = 500 fee = 4000000 - asset_type.append(3) + asset_types.append(3) is_rebasing[i] = True with boa.env.prank(deployer): pool = factory.deploy_plain_pool( - "test", - "test", - [t.address for t in pool_tokens], - A, - fee, - 866, - 0, - method_ids, - oracles, - asset_type, - is_rebasing, + "test", "test", [t.address for t in pool_tokens], A, fee, 866, 0, asset_types, method_ids, oracles ) return amm_interface_plain.at(pool) @@ -76,29 +66,29 @@ def swap( fee = 1000000 method_id = bytes(b"") oracle = zero_address - asset_type = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing + asset_types = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing is_rebasing = False metapool_token_type = pool_token_types[0] if metapool_token_type == 0: A = 2000 fee = 1000000 - asset_type = 0 + asset_types = 0 elif metapool_token_type == 1: A = 1000 fee = 3000000 - asset_type = 1 + asset_types = 1 elif metapool_token_type == 2: A = 1000 fee = 3000000 - asset_type = 2 + asset_types = 2 method_id = oracle_method_id oracle = underlying_tokens[0].address elif metapool_token_type == 3: A = 500 fee = 4000000 - asset_type = 3 + asset_types = 3 is_rebasing = True pool = factory.deploy_metapool( From a0c764af67d5934c65f272f8a9bfaa497a4d7b47 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:11:57 +0200 Subject: [PATCH 091/337] meta implementation fix, still too much contract size --- contracts/main/CurveStableSwapFactoryNG.vy | 19 +- contracts/main/CurveStableSwapMetaNG.vy | 292 ++++++++++++++++----- tests/fixtures/factory.py | 18 +- tests/fixtures/pools.py | 21 +- 4 files changed, 271 insertions(+), 79 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 0254c988..dc52738f 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -95,6 +95,7 @@ base_pool_assets: public(HashMap[address, bool]) # index -> implementation address pool_implementations: public(HashMap[uint256, address]) +metapool_implementations: public(HashMap[uint256, address]) gauge_implementation: public(address) views_implementation: public(address) @@ -627,7 +628,7 @@ def deploy_metapool( base_pool_n_coins: uint256 = len(self.base_pool_data[_base_pool].coins) assert base_pool_n_coins != 0, "Base pool is not added" - implementation: address = self.pool_implementations[_implementation_idx] + implementation: address = self.metapool_implementations[_implementation_idx] assert implementation != empty(address), "Invalid implementation index" # things break if a token has >18 decimals @@ -648,7 +649,6 @@ def deploy_metapool( _method_ids: DynArray[bytes4, MAX_COINS] = [_method_id, empty(bytes4)] _oracles: DynArray[address, MAX_COINS] = [_oracle, empty(address)] - # TODO: fix meta pool: address = create_from_blueprint( implementation, _name, # _name: String[32] @@ -780,6 +780,21 @@ def set_pool_implementations( self.pool_implementations[_implementation_index] = _implementation +@external +def set_metapool_implementations( + _implementation_index: uint256, + _implementation: address, +): + """ + @notice Set implementation contracts for metapools + @dev Only callable by admin + @param _implementation_index Implementation index where implementation is stored + @param _implementation Implementation address to use when deploying meta pools + """ + assert msg.sender == self.admin # dev: admin-only function + self.metapool_implementations[_implementation_index] = _implementation + + @external def set_gauge_implementation(_gauge_implementation: address): """ diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 469fb789..b09ac2e0 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -10,6 +10,12 @@ The Pool contract also records exponential moving averages for coins 1, 2 and 3 relative to coin 0. @dev Supports: +@dev Asset Types: + 0. Basic ERC20 token with no additional features + 1. WETH - can we directly converted to/from ETH + 2. Oracle - token with rate oracle + 3. Rebasing - token with rebase (e.g. stETH) + Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). 3. ERC20 tokens that rebase (either positive or fee on transfer) @@ -27,9 +33,9 @@ 6. Adds feature: `exchange_received`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. - Note: a. If pool contains rebasing tokens and `IS_REBASING` is True + Note: a. If pool contains rebasing tokens and one of the `asset_types` is 3 (Rebasing) then calling `exchange_received` will REVERT. - b. If pool contains rebasing token and `IS_REBASING` is False + b. If pool contains rebasing token and `asset_types` does not contain 3 (Rebasing) then this is an incorrect implementation and rebases can be stolen. 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output @@ -39,6 +45,7 @@ from vyper.interfaces import ERC20 +implements: ERC20 # ------------------------------- Interfaces --------------------------------- @@ -51,10 +58,8 @@ interface WETH: def deposit(): payable def withdraw(_amount: uint256): nonpayable -interface CurveLPToken: - def totalSupply() -> uint256: view - def mint(_to: address, _value: uint256) -> bool: nonpayable - def burnFrom(_to: address, _value: uint256) -> bool: nonpayable +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view @@ -156,7 +161,6 @@ WETH20: immutable(address) N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -lp_token: public(immutable(CurveLPToken)) # To denote that it is a plain pool: BASE_POOL: public(immutable(address)) @@ -167,7 +171,7 @@ factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -is_rebasing: HashMap[address, bool] +asset_types: public(DynArray[uint8, MAX_COINS]) FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -202,6 +206,29 @@ ma_last_time: public(uint256) # shift(2**32 - 1, 224) ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 +# --------------------------- ERC20 Specific Vars ---------------------------- + +name: public(immutable(String[64])) +symbol: public(immutable(String[32])) +decimals: public(constant(uint8)) = 18 +version: public(constant(String[8])) = "v7.0.0" + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +totalSupply: public(uint256) +nonces: public(HashMap[address, uint256]) + +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") +EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +VERSION_HASH: constant(bytes32) = keccak256(version) +NAME_HASH: immutable(bytes32) +CACHED_CHAIN_ID: immutable(uint256) +salt: public(immutable(bytes32)) +CACHED_DOMAIN_SEPARATOR: immutable(bytes32) + # ------------------------------ AMM Setup ----------------------------------- @@ -215,13 +242,12 @@ def __init__( _ma_exp_time: uint256, _weth: address, _base_pool: address, - _lp_token_implementation: address, _coins: DynArray[address, MAX_COINS], _base_coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], + _asset_types: DynArray[uint8, MAX_COINS], _method_ids: DynArray[bytes4, MAX_COINS], _oracles: DynArray[address, MAX_COINS], - _is_rebasing: DynArray[bool, MAX_COINS], ): """ @notice Initialize the pool contract @@ -239,12 +265,11 @@ def __init__( 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _asset_types Array of uint8 representing tokens in pool @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. - @param _is_rebasing Array of booleans where _is_rebasing[i] is True if _coins[i] is - a rebasing token: fee-on-transfer, tokens with slashing, positive rebasing, etc. """ WETH20 = _weth @@ -255,44 +280,20 @@ def __init__( __n_coins: uint256 = len(_coins) __base_n_coins: uint256 = len(_base_coins) BASE_N_COINS = __base_n_coins + assert __n_coins == 2 # dev: metapools can only have 2 coins - # ----------------- Parameters dependent of pool type -------------------- - - __rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - - if BASE_POOL != empty(address): - - assert __n_coins == 2 # dev: metapools can only have 2 coins - - self.is_rebasing[_coins[0]] = _is_rebasing[0] - # self.is_rebasing[_coins[1]] = False is ignored, - # because _coins[1] is base_lp_token and that does not rebase. - - for i in range(MAX_COINS): - if i < __base_n_coins: - self.is_rebasing[_base_coins[i]] = _is_rebasing[i+2] - - # Approval needed for add_liquidity operation on base pool in _exchange_underlying - ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - - if i < __n_coins: - self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) # <--- TODO: check this! - - # Only need rate_multiplier for coin paired against basepool's lp token: - __rate_multipliers = [_rate_multipliers[0]] - - else: + for i in range(MAX_COINS): + if i < __base_n_coins: + # Approval needed for add_liquidity operation on base pool in _exchange_underlying + ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - __n_coins = len(_coins) - for i in range(MAX_COINS): - if i == __n_coins: - break - self.last_prices_packed[i] = self.pack_prices(10**18, 10**18) - __rate_multipliers = _rate_multipliers + if i < __n_coins: + self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) # <--- TODO: check this! N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) - rate_multipliers = __rate_multipliers + rate_multipliers = _rate_multipliers + self.asset_types = _asset_types # ----------------- Parameters independent of pool type ------------------ @@ -319,12 +320,30 @@ def __init__( self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? - # --------------------------- Create LP Token ---------------------------- - - lp_token = CurveLPToken( - create_from_blueprint(_lp_token_implementation, _name, _symbol) + # --------------------------- ERC20 stuff ---------------------------- + + name = _name + symbol = _symbol + + # EIP712 related params ----------------- + NAME_HASH = keccak256(name) + salt = block.prevhash + CACHED_CHAIN_ID = chain.id + CACHED_DOMAIN_SEPARATOR = keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) ) + # ------------------------ Fire a transfer event ------------------------- + + log Transfer(empty(address), msg.sender, 0) + # ------------------ Token transfers in and out of the AMM ------------------- @@ -373,7 +392,7 @@ def _transfer_in( @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - incoming_coin_is_rebasing: bool = self.is_rebasing[coins[coin_idx]] + _incoming_coin_asset_type: uint8 = self.asset_types[coin_idx] # ------------------------- Handle Transfers ----------------------------- @@ -384,7 +403,7 @@ def _transfer_in( elif expect_optimistic_transfer: - assert not incoming_coin_is_rebasing, "exchange_received not allowed if incoming token is rebasing" + assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -409,7 +428,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if incoming_coin_is_rebasing: + if _incoming_coin_asset_type == 3: assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! else: assert dx == _dx, "Pool did not receive tokens for swap" @@ -758,7 +777,7 @@ def add_liquidity( # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) - total_supply: uint256 = lp_token.totalSupply() + total_supply: uint256 = self.totalSupply new_balances: DynArray[uint256, MAX_COINS] = old_balances # -------------------------- Do Transfers In ----------------------------- @@ -832,7 +851,9 @@ def add_liquidity( # Mint pool tokens total_supply += mint_amount - lp_token.mint(msg.sender, mint_amount) + self.balanceOf[_receiver] += mint_amount + self.totalSupply = total_supply + log Transfer(empty(address), _receiver, mint_amount) log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) @@ -864,13 +885,13 @@ def remove_liquidity_one_coin( self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR - lp_token.burnFrom(msg.sender, _burn_amount) + self._burnFrom(msg.sender, _burn_amount) log Transfer(msg.sender, empty(address), _burn_amount) self._transfer_out(i, dy, _use_eth, _receiver) - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, lp_token.totalSupply()) + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.totalSupply) self.save_p_from_price(p) @@ -932,12 +953,12 @@ def remove_liquidity_imbalance( self.save_p(new_balances, amp, D2) - total_supply: uint256 = lp_token.totalSupply() + total_supply: uint256 = self.totalSupply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" - lp_token.burnFrom(msg.sender, burn_amount) + self._burnFrom(msg.sender, burn_amount) log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) @@ -961,7 +982,7 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ - total_supply: uint256 = lp_token.totalSupply() + total_supply: uint256 = self.totalSupply amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -975,7 +996,7 @@ def remove_liquidity( amounts[i] = value self._transfer_out(i, value, _use_eth, _receiver) - lp_token.burnFrom(msg.sender, _burn_amount) # dev: insufficient funds + self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # TODO: check this! @@ -1134,7 +1155,7 @@ def _exchange_underlying( # for exchange_underlying, optimistic transfers need to be handled differently if expect_optimistic_transfer: - assert self.is_rebasing[input_coin] # dev: rebasing coins not supported + assert self.asset_types[i] != 3 # dev: rebasing coins not supported # This branch is never reached for rebasing tokens if input_coin == BASE_COINS[base_i]: @@ -1495,7 +1516,7 @@ def _calc_withdraw_one_coin( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) D0: uint256 = self.get_D(xp, amp) - total_supply: uint256 = lp_token.totalSupply() + total_supply: uint256 = self.totalSupply D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = self.get_y_D(amp, i, xp, D1) @@ -1723,6 +1744,151 @@ def exp(x: int256) -> uint256: return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) +# ---------------------------- ERC20 Utils ----------------------------------- + + +@view +@internal +def _domain_separator() -> bytes32: + if chain.id != CACHED_CHAIN_ID: + return keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + return CACHED_DOMAIN_SEPARATOR + + +@internal +def _transfer(_from: address, _to: address, _value: uint256): + # # NOTE: vyper does not allow underflows + # # so the following subtraction would revert on insufficient balance + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + + log Transfer(_from, _to, _value) + + +@internal +def _burnFrom(_from: address, _burn_amount: uint256): + + self.totalSupply -= _burn_amount + self.balanceOf[_from] -= _burn_amount + log Transfer(_from, empty(address), _burn_amount) + + +@external +def transfer(_to : address, _value : uint256) -> bool: + """ + @dev Transfer token for a specified address + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + self._transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from : address, _to : address, _value : uint256) -> bool: + """ + @dev Transfer tokens from one address to another. + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + self._transfer(_from, _to, _value) + + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != max_value(uint256): + self.allowance[_from][msg.sender] = _allowance - _value + + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk that + someone may use both the old and new allowance by unfortunate transaction + ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + + log Approval(msg.sender, _spender, _value) + return True + + +@external +def permit( + _owner: address, + _spender: address, + _value: uint256, + _deadline: uint256, + _v: uint8, + _r: bytes32, + _s: bytes32 +) -> bool: + """ + @notice Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 + @dev Supports smart contract wallets which implement ERC1271 + https://eips.ethereum.org/EIPS/eip-1271 + @param _owner The address which is a source of funds and has signed the Permit. + @param _spender The address which is allowed to spend the funds. + @param _value The amount of tokens to be spent. + @param _deadline The timestamp after which the Permit is no longer valid. + @param _v The bytes[64] of the valid secp256k1 signature of permit by owner + @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner + @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner + @return True, if transaction completes successfully + """ + assert _owner != empty(address) + assert block.timestamp <= _deadline + + nonce: uint256 = self.nonces[_owner] + digest: bytes32 = keccak256( + concat( + b"\x19\x01", + self._domain_separator(), + keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) + ) + ) + + if _owner.is_contract: + sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) + # reentrancy not a concern since this is a staticcall + assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL + else: + assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner + + self.allowance[_owner][_spender] = _value + self.nonces[_owner] = nonce + 1 + + log Approval(_owner, _spender, _value) + return True + + +@view +@external +def DOMAIN_SEPARATOR() -> bytes32: + """ + @notice EIP712 domain separator. + @return bytes32 Domain Separator set for the current chain. + """ + return self._domain_separator() + + # ------------------------- AMM View Functions ------------------------------- @@ -1780,7 +1946,7 @@ def get_virtual_price() -> uint256: D: uint256 = self.get_D(xp, amp) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio - return D * PRECISION / lp_token.totalSupply() + return D * PRECISION / self.totalSupply @view diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 27086eef..4f5a63b5 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -24,6 +24,17 @@ def amm_implementation(deployer, amm_interface): return amm_interface.deploy_as_blueprint() +@pytest.fixture(scope="module") +def amm_interface_meta(): + return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") + + +@pytest.fixture(scope="module") +def amm_implementation_meta(deployer, amm_interface_meta): + with boa.env.prank(deployer): + return amm_interface_meta.deploy_as_blueprint() + + @pytest.fixture(scope="module") def views_implementation(deployer): with boa.env.prank(deployer): @@ -54,6 +65,12 @@ def set_pool_implementations(owner, factory, amm_implementation): factory.set_pool_implementations(0, amm_implementation.address) +@pytest.fixture(scope="module") +def set_metapool_implementations(owner, factory, amm_implementation_meta): + with boa.env.prank(owner): + factory.set_pool_implementations(0, amm_implementation_meta.address) + + @pytest.fixture(scope="module") def add_base_pool( owner, @@ -69,7 +86,6 @@ def add_base_pool( [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), - [False] * len(base_pool_tokens), ) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index d37b9369..2041f260 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -26,7 +26,6 @@ def swap( method_ids = [bytes(b"")] * pool_size oracles = [zero_address] * pool_size asset_types = [] - is_rebasing = [False] * pool_size for i, t in enumerate(pool_token_types): if t == 0: @@ -47,7 +46,6 @@ def swap( A = 500 fee = 4000000 asset_types.append(3) - is_rebasing[i] = True with boa.env.prank(deployer): pool = factory.deploy_plain_pool( @@ -58,38 +56,36 @@ def swap( elif pool_type == 1: base_pool = request.getfixturevalue("base_pool") underlying_tokens = request.getfixturevalue("underlying_tokens") - amm_interface_meta = request.getfixturevalue("amm_interface") + amm_interface_meta = request.getfixturevalue("amm_interface_meta") _ = request.getfixturevalue("add_base_pool") - _ = request.getfixturevalue("set_pool_implementations") + _ = request.getfixturevalue("set_metapool_implementations") A = 2000 fee = 1000000 method_id = bytes(b"") oracle = zero_address - asset_types = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing - is_rebasing = False + asset_type = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing metapool_token_type = pool_token_types[0] if metapool_token_type == 0: A = 2000 fee = 1000000 - asset_types = 0 + asset_type = 0 elif metapool_token_type == 1: A = 1000 fee = 3000000 - asset_types = 1 + asset_type = 1 elif metapool_token_type == 2: A = 1000 fee = 3000000 - asset_types = 2 + asset_type = 2 method_id = oracle_method_id oracle = underlying_tokens[0].address elif metapool_token_type == 3: A = 500 fee = 4000000 - asset_types = 3 - is_rebasing = True + asset_type = 3 pool = factory.deploy_metapool( base_pool.address, @@ -100,10 +96,9 @@ def swap( fee, 866, 0, - 0, + asset_type, method_id, oracle, - is_rebasing, ) return amm_interface_meta.at(pool) From 0841f78bac4ec0b2bcbcd7b89116b5e9bbbab412 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:19:34 +0200 Subject: [PATCH 092/337] fixes and tests for basic pool --- .github/workflows/test_factory.yaml | 9 +++++---- .github/workflows/test_token.yaml | 9 +++++---- contracts/main/CurveStableSwapFactoryNG.vy | 16 +--------------- contracts/main/CurveStableSwapNG.vy | 7 +++++-- tests/test_factory.py | 9 ++++++--- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index 7e14f91f..64caf3bd 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -43,7 +43,8 @@ jobs: source .venv/bin/activate pytest tests/test_factory.py --pool-size=2 --pool-type=basic --decimals=18,18 -n auto - - name: Run Tests Meta - run: | - source .venv/bin/activate - pytest tests/test_factory.py --pool-size=2 --pool-type=meta --decimals=18,18 -n auto +# TODO: uncomment after meta fix +# - name: Run Tests Meta +# run: | +# source .venv/bin/activate +# pytest tests/test_factory.py --pool-size=2 --pool-type=meta --decimals=18,18 -n auto diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index a120b2af..93c5de01 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -34,7 +34,8 @@ jobs: source .venv/bin/activate pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto - - name: Run Test Meta - run: | - source .venv/bin/activate - pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto +# TODO: uncomment after meta fix +# - name: Run Test Meta +# run: | +# source .venv/bin/activate +# pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index dc52738f..86bc6a3e 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -449,11 +449,7 @@ def get_pool_asset_types(_pool: address) -> DynArray[uint8, MAX_COINS]: @param _pool Pool Address @return Integer indicating the pool asset type """ - base_pool: address = self.pool_data[_pool].base_pool - if base_pool == empty(address): - return self.pool_data[_pool].asset_types - else: - return self.base_pool_data[base_pool].asset_types + return self.pool_data[_pool].asset_types # <--- Pool Deployers ---> @@ -817,16 +813,6 @@ def set_views_implementation(_views_implementation: address): self.views_implementation = _views_implementation -@external -def set_pool_asset_types(_pool: address, _asset_types: DynArray[uint8, MAX_COINS]): - """ - @notice Set the asset type for factory pools - @dev Used to modify asset types that were set incorrectly at deployment - """ - assert msg.sender == self.admin # dev: admin-only function - self.pool_data[_pool].asset_types = _asset_types - - @external def commit_transfer_ownership(_addr: address): """ diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 25c53785..c9d5ca06 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -287,7 +287,10 @@ def __init__( assert i == 0, "ETH must be at index 0" self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? + + # --------------------------- initialize storage --------------------------- + self.stored_balances.append(0) + self.admin_balances.append(0) # --------------------------- ERC20 stuff ---------------------------- @@ -1195,7 +1198,7 @@ def _xp_mem( if i == N_COINS_128: break - result[i] = _rates[i] * _balances[i] / PRECISION + result.append(_rates[i] * _balances[i] / PRECISION) return result diff --git a/tests/test_factory.py b/tests/test_factory.py index 87ed79b7..978d321c 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -68,10 +68,12 @@ def test_get_implementation_address(self, factory, swap, amm_implementation): def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is False + def test_get_pool_types(self, factory, swap, pool_token_types): + assert factory.get_pool_asset_types(swap.address) == list(pool_token_types) + @pytest.mark.only_for_pool_type(1) class TestMeta: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - @pytest.mark.only_for_token_types(0, 2, 3) def test_find_pool_for_coins(self, factory, swap, underlying_tokens, sending, receiving): assert ( factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) @@ -119,8 +121,9 @@ def test_get_coin_indices_reverts(self, factory, swap, base_pool_lp_token, under with boa.reverts(): factory.get_coin_indices(swap.address, base_pool_lp_token.address, underlying_tokens[idx]) - def test_get_implementation_address(self, factory, swap, amm_implementation_meta): - assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address + # TODO: return after meta is fixed + # def test_get_implementation_address(self, factory, swap, amm_implementation_meta): + # assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is True From 81b16f45ad412504f2a355e11d3a0776a8825ff0 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:57:33 +0200 Subject: [PATCH 093/337] add integration tests --- tests/test_factory.py | 312 ++++++++++++++++++++++-------------------- 1 file changed, 161 insertions(+), 151 deletions(-) diff --git a/tests/test_factory.py b/tests/test_factory.py index 978d321c..e5cea06b 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -128,154 +128,164 @@ def test_get_coin_indices_reverts(self, factory, swap, base_pool_lp_token, under def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is True - # @pytest.mark.usefixtures("forked_chain") - # @pytest.mark.only_for_pool_type(0) - # class TestFactoryAddPools: - # def test_add_base_pool(self, factory, alice, fee_receiver, implementation_usd): - # susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" - # factory.add_base_pool( - # susd_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # assert factory.base_pool_count() == 3 - # assert factory.base_pool_list(2) == susd_pool - # assert factory.get_fee_receiver(susd_pool) == fee_receiver - # - # def test_add_base_pool_already_exists(self, factory, base_pool, alice, fee_receiver, implementation_usd): - # with boa.reverts("dev: pool exists"): - # factory.add_base_pool( - # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # - # def test_add_base_pool_only_admin(factory, base_pool, bob, fee_receiver, implementation_usd): - # with brownie.reverts("dev: admin-only function"): - # factory.add_base_pool( - # "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD", - # fee_receiver, - # 0, - # [implementation_usd] + [ZERO_ADDRESS] * 9, - # {"from": bob}, - # ) - # - # @pytest.mark.skip - # def test_deploy_metapool(MetaUSD, new_factory, new_factory_setup, base_pool, bob): - # coin = ERC20(decimals=7) - # - # tx = new_factory.deploy_metapool( - # base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob} - # ) - # assert tx.return_value == tx.new_contracts[0] - # swap = MetaUSD.at(tx.return_value) - # - # assert swap.coins(0) == coin - # assert swap.A() == 12345 - # assert swap.fee() == 50000000 - # - # assert new_factory.pool_count() == 1 - # assert new_factory.pool_list(0) == swap - # assert new_factory.get_decimals(swap) == [7, 18, 0, 0] - # - # - # @pytest.mark.skip - # def test_add_existing_metapools( - # factory, new_factory, fee_receiver, implementation_usd, base_pool, alice - # ): - # assert new_factory.pool_count() == 0 - # # add existing USD pools to new factory - # new_factory.add_base_pool( - # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # new_factory.add_existing_metapools( - # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B", "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c"] - # + [ZERO_ADDRESS] * 8 - # ) - # assert new_factory.pool_count() == 2 - # assert new_factory.pool_list(0) == "0x5a6A4D54456819380173272A5E8E9B9904BdF41B" - # assert new_factory.pool_list(1) == "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c" - # assert ( - # new_factory.get_implementation_address("0x5a6A4D54456819380173272A5E8E9B9904BdF41B") - # == "0x5F890841f657d90E081bAbdB532A05996Af79Fe6" - # ) - # - # - # @pytest.mark.skip - # def test_add_existing_metapools_unknown_pool(swap, new_factory): - # with brownie.reverts("dev: pool not in old factory"): - # new_factory.add_existing_metapools([swap] + [ZERO_ADDRESS] * 9) - # - # - # @pytest.mark.skip - # def test_add_existing_metapools_duplicate_pool( - # new_factory, base_pool, implementation_usd, fee_receiver, alice - # ): - # new_factory.add_base_pool( - # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # new_factory.add_existing_metapools( - # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 - # ) - # with brownie.reverts("dev: pool already exists"): - # new_factory.add_existing_metapools( - # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 - # ) - # - # - # def test_deploy_plain_pool( - # new_factory_setup, new_factory, decimals, bob, plain_basic, project, coins - # ): - # - # tx = new_factory.deploy_plain_pool( - # "Test Plain", - # "TST", - # coins + [ZERO_ADDRESS] * (4 - len(coins)), - # 12345, - # 50000000, - # 0, - # 0, - # {"from": bob}, - # ) - # assert tx.return_value == tx.new_contracts[0] - # swap = getattr(project, plain_basic._name).at(tx.return_value) - # - # assert swap.coins(0) == coins[0] - # assert swap.coins(1) == coins[1] - # - # assert swap.A() == 12345 - # assert swap.fee() == 50000000 - # - # assert new_factory.pool_count() == 1 - # assert new_factory.pool_list(0) == swap - # assert new_factory.get_decimals(swap) == decimals + [0] * (4 - len(decimals)) - # - # - # @pytest.mark.skip - # def test_pool_count(new_factory, coins, new_factory_setup, bob, base_pool): - # - # tx = new_factory.deploy_plain_pool( - # "Test Plain", "TST", coins, 12345, 50000000, 0, 0, {"from": bob} - # ) - # assert tx.return_value == tx.new_contracts[0] - # assert new_factory.pool_count() == 1 - # - # coin = ERC20(decimals=7) - # tx = new_factory.deploy_metapool( - # base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob} - # ) - # assert tx.return_value == tx.new_contracts[0] - # assert new_factory.pool_count() == 2 - # - # coin = ERC20(decimals=7) - # pool = Contract("0x7fC77b5c7614E1533320Ea6DDc2Eb61fa00A9714") - # tx = new_factory.deploy_metapool(pool, "Name", "SYM", coin, 123456, 50000000, 0, {"from": bob}) - # assert new_factory.pool_count() == 3 - # - # - # @pytest.mark.skip - # def test_deploy_plain_pool_revert(base_pool, new_factory, new_factory_setup, bob): - # coin = ERC20(decimals=7) - # new_factory.deploy_metapool(base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob}) - # existing_coin = base_pool.coins(0) - # assert new_factory.base_pool_assets(existing_coin) - # coins = [existing_coin, ERC20(decimals=9), ZERO_ADDRESS, ZERO_ADDRESS] - # # should revert because a metapool already exists for one of the coins - # with brownie.reverts("Invalid asset, deploy a metapool"): - # new_factory.deploy_plain_pool("Test Plain", "TST", coins, 12345, 50000000, {"from": bob}) + @pytest.mark.usefixtures("forked_chain") + @pytest.mark.only_for_pool_type(0) + class TestFactoryAddPools: + def test_add_base_pool(self, factory, owner, add_base_pool): + susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" + lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" + coins = [ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", + ] + + factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) + assert factory.base_pool_count() == 2 + assert factory.base_pool_list(1) == susd_pool + + def test_add_base_pool_already_exists( + self, + owner, + factory, + add_base_pool, + base_pool, + base_pool_lp_token, + base_pool_tokens, + ): + with boa.reverts(): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + [t.address for t in base_pool_tokens], + [0] * len(base_pool_tokens), + len(base_pool_tokens), + sender=owner, + ) + + def test_add_base_pool_only_admin( + self, + factory, + bob, + base_pool, + base_pool_lp_token, + base_pool_tokens, + ): + with boa.reverts(): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + [t.address for t in base_pool_tokens], + [0] * len(base_pool_tokens), + len(base_pool_tokens), + sender=bob, + ) + + # @pytest.mark.skip + # def test_deploy_metapool(MetaUSD, new_factory, new_factory_setup, base_pool, bob): + # coin = ERC20(decimals=7) + # + # tx = new_factory.deploy_metapool(base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob}) + # assert tx.return_value == tx.new_contracts[0] + # swap = MetaUSD.at(tx.return_value) + # + # assert swap.coins(0) == coin + # assert swap.A() == 12345 + # assert swap.fee() == 50000000 + # + # assert new_factory.pool_count() == 1 + # assert new_factory.pool_list(0) == swap + # assert new_factory.get_decimals(swap) == [7, 18, 0, 0] + # + # @pytest.mark.skip + # def test_add_existing_metapools(factory, new_factory, fee_receiver, implementation_usd, base_pool, alice): + # assert new_factory.pool_count() == 0 + # # add existing USD pools to new factory + # new_factory.add_base_pool( + # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # new_factory.add_existing_metapools( + # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B", "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c"] + # + [ZERO_ADDRESS] * 8 + # ) + # assert new_factory.pool_count() == 2 + # assert new_factory.pool_list(0) == "0x5a6A4D54456819380173272A5E8E9B9904BdF41B" + # assert new_factory.pool_list(1) == "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c" + # assert ( + # new_factory.get_implementation_address("0x5a6A4D54456819380173272A5E8E9B9904BdF41B") + # == "0x5F890841f657d90E081bAbdB532A05996Af79Fe6" + # ) + # + # @pytest.mark.skip + # def test_add_existing_metapools_unknown_pool(swap, new_factory): + # with brownie.reverts("dev: pool not in old factory"): + # new_factory.add_existing_metapools([swap] + [ZERO_ADDRESS] * 9) + # + # @pytest.mark.skip + # def test_add_existing_metapools_duplicate_pool(new_factory, base_pool, implementation_usd, fee_receiver, + # alice): + # new_factory.add_base_pool( + # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} + # ) + # new_factory.add_existing_metapools(["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9) + # with brownie.reverts("dev: pool already exists"): + # new_factory.add_existing_metapools( + # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 + # ) + + def test_deploy_plain_pool( + self, factory, amm_interface, set_pool_implementations, pool_tokens, pool_size, zero_address + ): + swap_address = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + 2000, + 1000000, + 866, + 0, + [0] * pool_size, + [bytes(b"")] * pool_size, + [zero_address] * pool_size, + ) + assert swap_address != zero_address + + swap = amm_interface.at(swap_address) + assert swap.coins(0) == pool_tokens[0].address + assert swap.coins(1) == pool_tokens[1].address + + assert swap.A() == 2000 + assert swap.fee() == 1000000 + + assert factory.pool_count() == 1 + assert factory.pool_list(0) == swap.address + assert factory.get_decimals(swap) == [t.decimals() for t in pool_tokens] + + @pytest.mark.skip + def test_pool_count( + self, + factory, + swap, + add_base_pool, + amm_interface, + set_pool_implementations, + pool_tokens, + pool_size, + zero_address, + ): + assert factory.pool_count() == 2 + + _ = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + 2000, + 1000000, + 866, + 0, + [0] * pool_size, + [bytes(b"")] * pool_size, + [zero_address] * pool_size, + ) + assert factory.pool_count() == 3 From a444d4075e283439f651513cd4f29111dfd04a8e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:41:40 +0200 Subject: [PATCH 094/337] metang compiles --- contracts/main/CurveStableSwapMetaNG.vy | 149 +++++++----------------- 1 file changed, 39 insertions(+), 110 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b09ac2e0..b909811c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -158,8 +158,8 @@ MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 # ---------------------------- Pool Variables -------------------------------- WETH20: immutable(address) -N_COINS: public(immutable(uint256)) -N_COINS_128: immutable(int128) +N_COINS: public(constant(uint256)) = 2 +N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 # To denote that it is a plain pool: @@ -171,7 +171,7 @@ factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -asset_types: public(DynArray[uint8, MAX_COINS]) +asset_types: public(immutable(DynArray[uint8, MAX_COINS])) FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -275,25 +275,17 @@ def __init__( WETH20 = _weth BASE_POOL = _base_pool BASE_COINS = _base_coins + BASE_N_COINS = len(_base_coins) coins = _coins - - __n_coins: uint256 = len(_coins) - __base_n_coins: uint256 = len(_base_coins) - BASE_N_COINS = __base_n_coins - assert __n_coins == 2 # dev: metapools can only have 2 coins + rate_multipliers = _rate_multipliers + asset_types = _asset_types # contains asset types for all pool tokens including base pool tokens for i in range(MAX_COINS): - if i < __base_n_coins: + if i < BASE_N_COINS: # Approval needed for add_liquidity operation on base pool in _exchange_underlying ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - if i < __n_coins: - self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) # <--- TODO: check this! - - N_COINS = __n_coins - N_COINS_128 = convert(__n_coins, int128) - rate_multipliers = _rate_multipliers - self.asset_types = _asset_types + self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) # ----------------- Parameters independent of pool type ------------------ @@ -308,10 +300,7 @@ def __init__( self.ma_exp_time = _ma_exp_time self.ma_last_time = block.timestamp - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): # Enforce native token as coin[0] if _coins[i] == WETH20: @@ -392,7 +381,7 @@ def _transfer_in( @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - _incoming_coin_asset_type: uint8 = self.asset_types[coin_idx] + _incoming_coin_asset_type: uint8 = asset_types[coin_idx] # ------------------------- Handle Transfers ----------------------------- @@ -492,10 +481,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: oracles: DynArray[uint256, MAX_COINS] = self.oracles - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): if oracles[i] == 0: continue @@ -523,11 +509,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - + for i in range(N_COINS_128): result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) return result @@ -782,10 +764,7 @@ def add_liquidity( # -------------------------- Do Transfers In ----------------------------- - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): if _amounts[i] > 0: @@ -821,10 +800,7 @@ def add_liquidity( # Only account for fees if we are not the first to deposit base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 @@ -919,10 +895,7 @@ def remove_liquidity_imbalance( D0: uint256 = self.get_D_mem(rates, old_balances, amp) new_balances: DynArray[uint256, MAX_COINS] = old_balances - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): if _amounts[i] != 0: new_balances[i] -= _amounts[i] @@ -932,10 +905,7 @@ def remove_liquidity_imbalance( fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 @@ -986,10 +956,7 @@ def remove_liquidity( amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" @@ -1155,7 +1122,7 @@ def _exchange_underlying( # for exchange_underlying, optimistic transfers need to be handled differently if expect_optimistic_transfer: - assert self.asset_types[i] != 3 # dev: rebasing coins not supported + assert asset_types[i] != 3 # dev: rebasing coins not supported # This branch is never reached for rebasing tokens if input_coin == BASE_COINS[base_i]: @@ -1262,10 +1229,7 @@ def _withdraw_admin_fees(): assert fee_receiver != empty(address) # dev: fee receiver not set admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): if admin_balances[i] > 0: @@ -1318,10 +1282,7 @@ def get_y( c: uint256 = D Ann: uint256 = amp * N_COINS - for _i in range(MAX_COINS_128): - - if _i == N_COINS_128: - break + for _i in range(N_COINS_128): if _i == i: _x = x @@ -1417,10 +1378,7 @@ def get_y_D( c: uint256 = D Ann: uint256 = A * N_COINS - for _i in range(MAX_COINS_128): - - if _i == N_COINS_128: - break + for _i in range(N_COINS_128): if _i != i: _x = xp[_i] @@ -1477,10 +1435,7 @@ def _xp_mem( result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128): result[i] = _rates[i] * _balances[i] / PRECISION @@ -1523,10 +1478,7 @@ def _calc_withdraw_one_coin( base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for j in range(MAX_COINS_128): - - if j == N_COINS_128: - break + for j in range(N_COINS_128): dx_expected: uint256 = 0 xp_j: uint256 = xp[j] @@ -1570,20 +1522,12 @@ def _get_p( ANN: uint256 = unsafe_mul(amp, N_COINS) Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - + for i in range(N_COINS_128): Dr = Dr * D / xp[i] p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) xp0_A: uint256 = ANN * xp[0] / A_PRECISION - for i in range(1, MAX_COINS): - if i == N_COINS: - break - - p[i - 1] = 10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr) + p.append(10**18 * (xp0_A + Dr * xp[0] / xp[1]) / (xp0_A + Dr)) return p @@ -1593,20 +1537,14 @@ def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): """ Saves current price and its EMA """ - ma_last_time: uint256 = self.ma_last_time + if last_prices[0] != 0: - for i in range(MAX_COINS): - if i == N_COINS - 1: - break + # Upate packed prices ----------------- + self.last_prices_packed[0] = self.pack_prices(last_prices[0], self._ma_price()) - if last_prices[i] != 0: - - # Upate packed prices ----------------- - self.last_prices_packed[i] = self.pack_prices(last_prices[i], self._ma_price(i)) - - # Update ma_last_time ------------------ - if ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + # Update ma_last_time ------------------ + if self.ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp @internal @@ -1619,10 +1557,10 @@ def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): @internal @view -def _ma_price(i: uint256) -> uint256: +def _ma_price() -> uint256: ma_last_time: uint256 = self.ma_last_time - pp: uint256 = self.last_prices_packed[i] + pp: uint256 = self.last_prices_packed[0] last_price: uint256 = pp & (2**128 - 1) last_ema_price: uint256 = (pp >> 128) @@ -1637,13 +1575,13 @@ def _ma_price(i: uint256) -> uint256: @view @external def last_price(i: uint256) -> uint256: - return self.last_prices_packed[i] & (2**128 - 1) + return self.last_prices_packed[0] & (2**128 - 1) @view @external def ema_price(i: uint256) -> uint256: - return (self.last_prices_packed[i] >> 128) + return (self.last_prices_packed[0] >> 128) @external @@ -1660,14 +1598,14 @@ def get_p(i: uint256) -> uint256: self._stored_rates(), self._balances() ) D: uint256 = self.get_D(xp, amp) - return self._get_p(xp, amp, D)[i] + return self._get_p(xp, amp, D)[0] @external @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - return self._ma_price(i) + return self._ma_price() # ----------------------------- Math Utils ----------------------------------- @@ -1961,17 +1899,8 @@ def calc_token_amount( @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - amounts[i] = _amounts[i] - views: address = factory.views_implementation() - return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) + return StableSwapViews(views).calc_token_amount(_amounts, _is_deposit, self) @view @@ -2013,7 +1942,7 @@ def get_balances() -> DynArray[uint256, MAX_COINS]: @view @external def oracle(_idx: uint256) -> address: - return convert(self.oracles[_idx] % 2**160, address) + return convert(self.oracles[0] % 2**160, address) @view From 6534548a3147b8dc82e1e8581e506a50a382cda3 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:53:26 +0200 Subject: [PATCH 095/337] meta-ng fits? --- contracts/main/CurveStableSwapMetaNG.vy | 46 +++---------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b909811c..9796bdb3 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -157,7 +157,6 @@ MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 # ---------------------------- Pool Variables -------------------------------- -WETH20: immutable(address) N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 @@ -240,7 +239,6 @@ def __init__( _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _weth: address, _base_pool: address, _coins: DynArray[address, MAX_COINS], _base_coins: DynArray[address, MAX_COINS], @@ -272,7 +270,6 @@ def __init__( @param _oracles Array of rate oracle addresses. """ - WETH20 = _weth BASE_POOL = _base_pool BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) @@ -302,10 +299,6 @@ def __init__( for i in range(N_COINS_128): - # Enforce native token as coin[0] - if _coins[i] == WETH20: - assert i == 0, "ETH must be at index 0" - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? @@ -337,19 +330,11 @@ def __init__( # ------------------ Token transfers in and out of the AMM ------------------- -@payable -@external -def __default__(): - if msg.value > 0: - assert WETH20 in coins - - @internal def _transfer_in( coin_idx: int128, dx: uint256, dy: uint256, - mvalue: uint256, callbacker: address, callback_sig: bytes32, sender: address, @@ -372,7 +357,6 @@ def _transfer_in( @params _coin address of the coin to transfer in. @params dx amount of `_coin` to transfer into the pool. @params dy amount of `_coin` to transfer out of the pool. - @params mvalue msg.value if the transfer is ETH, 0 otherwise. @params callbacker address to call `callback_sig` on. @params callback_sig signature of the callback function. @params sender address to transfer `_coin` from. @@ -385,12 +369,7 @@ def _transfer_in( # ------------------------- Handle Transfers ----------------------------- - if use_eth and coins[coin_idx] == WETH20: - - _dx = mvalue - WETH(WETH20).deposit(value=dx) - - elif expect_optimistic_transfer: + if expect_optimistic_transfer: assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] @@ -445,16 +424,9 @@ def _transfer_out( # ------------------------- Handle Transfers ----------------------------- - if use_eth and coins[_coin_idx] == WETH20: - - WETH(WETH20).withdraw(_amount) - raw_call(receiver, b"", value=_amount) - - else: - - assert ERC20(coins[_coin_idx]).transfer( - receiver, _amount, default_return_value=True - ) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) # ----------------------- Update Stored Balances ------------------------- @@ -508,7 +480,6 @@ def _balances() -> DynArray[uint256, MAX_COINS]: @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(N_COINS_128): result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) @@ -518,7 +489,6 @@ def _balances() -> DynArray[uint256, MAX_COINS]: # -------------------------- AMM Main Functions ------------------------------ -@payable @external @nonreentrant('lock') def exchange( @@ -542,7 +512,6 @@ def exchange( """ return self._exchange( msg.sender, - msg.value, i, j, _dx, @@ -581,7 +550,6 @@ def exchange_extended( assert _cb != empty(bytes32) # dev: No callback specified return self._exchange( _sender, - 0, # mvalue is zero here i, j, _dx, @@ -620,7 +588,6 @@ def exchange_received( """ return self._exchange( msg.sender, - 0, i, j, _dx, @@ -736,7 +703,6 @@ def exchange_underlying_received( ) -@payable @external @nonreentrant('lock') def add_liquidity( @@ -772,7 +738,6 @@ def add_liquidity( i, _amounts[i], 0, - msg.value, empty(address), empty(bytes32), msg.sender, @@ -1022,7 +987,6 @@ def __exchange( @internal def _exchange( sender: address, - mvalue: uint256, i: int128, j: int128, _dx: uint256, @@ -1048,7 +1012,6 @@ def _exchange( i, _dx, _min_dy, - mvalue, callbacker, callback_sig, sender, @@ -1142,7 +1105,6 @@ def _exchange_underlying( i, _dx, _min_dy, - 0, # msg.value is always 0 for exchange_underlying callbacker, callback_sig, sender, From a166579cab09b232c339284668393ec994216317 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:59:44 +0200 Subject: [PATCH 096/337] math contract --- .github/workflows/test_token.yaml | 10 +- contracts/main/CurveStableSwapMetaNG.vy | 491 +++++++----------------- contracts/main/CurveStableSwapNGMath.vy | 245 ++++++++++++ poetry.lock | 124 +++++- pyproject.toml | 2 + tests/conftest.py | 2 +- 6 files changed, 515 insertions(+), 359 deletions(-) create mode 100644 contracts/main/CurveStableSwapNGMath.vy diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index 93c5de01..1a7a1dce 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -34,8 +34,8 @@ jobs: source .venv/bin/activate pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto -# TODO: uncomment after meta fix -# - name: Run Test Meta -# run: | -# source .venv/bin/activate -# pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto + TODO: uncomment after meta fix + - name: Run Test Meta + run: | + source .venv/bin/activate + pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9796bdb3..e411b048 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -84,6 +84,24 @@ interface StableSwap: def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable def get_virtual_price() -> uint256: view +interface Math: + def get_y( + i: int128, + j: int128, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _D: uint256 + ) -> uint256: view + def get_y_D( + A: uint256, + i: int128, + xp: DynArray[uint256, MAX_COINS], + D: uint256 + ) -> uint256: view + def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: view + def exp(x: int256) -> uint256: view + # --------------------------------- Events ----------------------------------- event Transfer: @@ -166,6 +184,7 @@ BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) +math: public(immutable(Math)) factory: public(immutable(Factory)) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] @@ -239,6 +258,7 @@ def __init__( _A: uint256, _fee: uint256, _ma_exp_time: uint256, + _math_implementation: address, _base_pool: address, _coins: DynArray[address, MAX_COINS], _base_coins: DynArray[address, MAX_COINS], @@ -270,6 +290,7 @@ def __init__( @param _oracles Array of rate oracle addresses. """ + math = Math(_math_implementation) BASE_POOL = _base_pool BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) @@ -300,7 +321,7 @@ def __init__( for i in range(N_COINS_128): self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - self.admin_balances.append(0) # <--- this initialises storage for admin balances # TODO: check if this is needed? + self.admin_balances.append(0) # <--- this initialises storage for admin balances # --------------------------- ERC20 stuff ---------------------------- @@ -780,7 +801,7 @@ def add_liquidity( new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) + D2: uint256 = math.get_D(xp, amp) mint_amount = total_supply * (D2 - D0) / D0 self.save_p(xp, amp, D2) @@ -961,8 +982,8 @@ def __exchange( ) -> uint256: amp: uint256 = self._A() - D: uint256 = self.get_D(_xp, amp) - y: uint256 = self.get_y(i, j, x, _xp, amp, D) + D: uint256 = math.get_D(_xp, amp) + y: uint256 = math.get_y(i, j, x, _xp, amp, D) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR @@ -1037,151 +1058,151 @@ def _exchange( return dy -@internal -def _exchange_underlying( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - callbacker: address, - callback_sig: bytes32, - expect_optimistic_transfer: bool = False -) -> uint256: +# @internal +# def _exchange_underlying( +# sender: address, +# i: int128, +# j: int128, +# _dx: uint256, +# _min_dy: uint256, +# receiver: address, +# callbacker: address, +# callback_sig: bytes32, +# expect_optimistic_transfer: bool = False +# ) -> uint256: - assert BASE_POOL != empty(address) # dev: pool is not a metapool +# assert BASE_POOL != empty(address) # dev: pool is not a metapool - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) +# rates: DynArray[uint256, MAX_COINS] = self._stored_rates() +# old_balances: DynArray[uint256, MAX_COINS] = self._balances() +# xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) - dy: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - x: uint256 = 0 - input_coin: address = empty(address) - output_coin: address = empty(address) - - if i == 0: - input_coin = coins[0] - else: - base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts - meta_i = 1 - input_coin = BASE_COINS[base_i] - if j == 0: - output_coin = coins[0] - else: - base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts - meta_j = 1 - output_coin = BASE_COINS[base_j] +# dy: uint256 = 0 +# base_i: int128 = 0 +# base_j: int128 = 0 +# meta_i: int128 = 0 +# meta_j: int128 = 0 +# x: uint256 = 0 +# input_coin: address = empty(address) +# output_coin: address = empty(address) - # --------------------------- Do Transfer in ----------------------------- +# if i == 0: +# input_coin = coins[0] +# else: +# base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts +# meta_i = 1 +# input_coin = BASE_COINS[base_i] +# if j == 0: +# output_coin = coins[0] +# else: +# base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts +# meta_j = 1 +# output_coin = BASE_COINS[base_j] - dx_w_fee: uint256 = 0 +# # --------------------------- Do Transfer in ----------------------------- - # for exchange_underlying, optimistic transfers need to be handled differently - if expect_optimistic_transfer: +# dx_w_fee: uint256 = 0 - assert asset_types[i] != 3 # dev: rebasing coins not supported +# # for exchange_underlying, optimistic transfers need to be handled differently +# if expect_optimistic_transfer: - # This branch is never reached for rebasing tokens - if input_coin == BASE_COINS[base_i]: - # we expect base_coin's balance to be 0. So swap whatever base_coin's - # balance the pool has: - dx_w_fee = ERC20(input_coin).balanceOf(self) - else: - dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] - assert dx_w_fee == _dx - self.stored_balances[meta_i] += dx_w_fee +# assert asset_types[i] != 3 # dev: rebasing coins not supported - dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx +# # This branch is never reached for rebasing tokens +# if input_coin == BASE_COINS[base_i]: +# # we expect base_coin's balance to be 0. So swap whatever base_coin's +# # balance the pool has: +# dx_w_fee = ERC20(input_coin).balanceOf(self) +# else: +# dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] +# assert dx_w_fee == _dx +# self.stored_balances[meta_i] += dx_w_fee - else: +# dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx - dx_w_fee = self._transfer_in( - i, - _dx, - _min_dy, - callbacker, - callback_sig, - sender, - receiver, - False, # use_eth = False - False, # expect_optimistic_transfer = False - ) +# else: - # ------------------------------------------------------------------------ +# dx_w_fee = self._transfer_in( +# i, +# _dx, +# _min_dy, +# callbacker, +# callback_sig, +# sender, +# receiver, +# False, # use_eth = False +# False, # expect_optimistic_transfer = False +# ) - if i == 0 or j == 0: # meta swap +# # ------------------------------------------------------------------------ - if i == 0: +# if i == 0 or j == 0: # meta swap - x = xp[i] + dx_w_fee * rates[i] / PRECISION +# if i == 0: - else: +# x = xp[i] + dx_w_fee * rates[i] / PRECISION - dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION - x += xp[MAX_METAPOOL_COIN_INDEX] +# else: - dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) +# dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) +# x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION +# x += xp[MAX_METAPOOL_COIN_INDEX] - # Withdraw from the base pool if needed - if j > 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount +# dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) - assert dy >= _min_dy +# # Withdraw from the base pool if needed +# if j > 0: +# out_amount: uint256 = ERC20(output_coin).balanceOf(self) +# StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) +# dy = ERC20(output_coin).balanceOf(self) - out_amount - # Adjust stored balances: - self.stored_balances[meta_j] -= dy +# assert dy >= _min_dy - else: # base pool swap (user should swap at base pool for better gas) +# # Adjust stored balances: +# self.stored_balances[meta_j] -= dy - dy = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy +# else: # base pool swap (user should swap at base pool for better gas) - # --------------------------- Do Transfer out ---------------------------- +# dy = ERC20(output_coin).balanceOf(self) +# StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) +# dy = ERC20(output_coin).balanceOf(self) - dy - assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) +# # --------------------------- Do Transfer out ---------------------------- - # ------------------------------------------------------------------------ +# assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) - log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! +# # ------------------------------------------------------------------------ - return dy +# log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! +# return dy -@internal -def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] - x: uint256 = ERC20(coin_i).balanceOf(self) +# @internal +# def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: - if BASE_N_COINS == 2: +# coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] +# x: uint256 = ERC20(coin_i).balanceOf(self) - base_inputs: uint256[2] = empty(uint256[2]) - base_inputs[base_i] = dx - StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) +# if BASE_N_COINS == 2: - if BASE_N_COINS == 3: +# base_inputs: uint256[2] = empty(uint256[2]) +# base_inputs[base_i] = dx +# StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) - base_inputs: uint256[3] = empty(uint256[3]) - base_inputs[base_i] = dx - StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) +# if BASE_N_COINS == 3: - else: +# base_inputs: uint256[3] = empty(uint256[3]) +# base_inputs[base_i] = dx +# StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) +# else: - return ERC20(coin_i).balanceOf(self) - x +# base_inputs: uint256[4] = empty(uint256[4]) +# base_inputs[base_i] = dx +# StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) + +# return ERC20(coin_i).balanceOf(self) - x @internal @@ -1204,168 +1225,6 @@ def _withdraw_admin_fees(): # --------------------------- AMM Math Functions ----------------------------- -@view -@internal -def get_y( - i: int128, - j: int128, - x: uint256, - xp: DynArray[uint256, MAX_COINS], - _amp: uint256, - _D: uint256 -) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS_128 # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS_128 - - amp: uint256 = _amp - D: uint256 = _D - if _D == 0: - amp = self._A() - D = self.get_D(xp, amp) - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - for _i in range(N_COINS_128): - - if _i == i: - _x = x - elif _i != j: - _x = xp[_i] - else: - continue - - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - y: uint256 = D - - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@pure -@internal -def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - for x in _xp: - S += x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = _amp * N_COINS - D_P: uint256 = 0 - Dprev: uint256 = 0 - - for i in range(255): - D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) - Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) - # Equality with the precision of 1 - if D > Dprev: - if D - Dprev <= 1: - return D - else: - if Dprev - D <= 1: - return D - # convergence typically occurs in 4 rounds or less, this should be unreachable! - # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` - raise - - -@pure -@internal -def get_y_D( - A: uint256, - i: int128, - xp: DynArray[uint256, MAX_COINS], - D: uint256 -) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS_128 # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = A * N_COINS - - for _i in range(N_COINS_128): - - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - y: uint256 = D - - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise - - @view @internal def _A() -> uint256: @@ -1396,9 +1255,7 @@ def _xp_mem( ) -> DynArray[uint256, MAX_COINS]: result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(N_COINS_128): - result[i] = _rates[i] * _balances[i] / PRECISION return result @@ -1412,7 +1269,7 @@ def get_D_mem( _amp: uint256 ) -> uint256: xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) - return self.get_D(xp, _amp) + return math.get_D(xp, _amp) @view @@ -1431,11 +1288,11 @@ def _calc_withdraw_one_coin( amp: uint256 = self._A() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) - D0: uint256 = self.get_D(xp, amp) + D0: uint256 = math.get_D(xp, amp) total_supply: uint256 = self.totalSupply D1: uint256 = D0 - _burn_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) + new_y: uint256 = math.get_y_D(amp, i, xp, D1) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -1450,7 +1307,7 @@ def _calc_withdraw_one_coin( dx_expected = xp_j - xp_j * D1 / D0 xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) + dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors @@ -1527,7 +1384,7 @@ def _ma_price() -> uint256: last_ema_price: uint256 = (pp >> 128) if ma_last_time < block.timestamp: - alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) + alpha: uint256 = math.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 else: @@ -1559,7 +1416,7 @@ def get_p(i: uint256) -> uint256: xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() ) - D: uint256 = self.get_D(xp, amp) + D: uint256 = math.get_D(xp, amp) return self._get_p(xp, amp, D)[0] @@ -1573,76 +1430,6 @@ def price_oracle(i: uint256) -> uint256: # ----------------------------- Math Utils ----------------------------------- -@internal -@pure -def exp(x: int256) -> uint256: - - """ - @dev Calculates the natural exponential function of a signed integer with - a precision of 1e18. - @notice Note that this function consumes about 810 gas units. The implementation - is inspired by Remco Bloemen's implementation under the MIT license here: - https://xn--2-umb.com/22/exp-ln. - @dev This implementation is derived from Snekmate, which is authored - by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. - https://github.com/pcaversaccio/snekmate - @param x The 32-byte variable. - @return int256 The 32-byte calculation result. - """ - value: int256 = x - - # If the result is `< 0.5`, we return zero. This happens when we have the following: - # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". - if (x <= -42139678854452767551): - return empty(uint256) - - # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. - # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". - assert x < 135305999368893231589, "wad_exp overflow" - - # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher - # intermediate precision and a binary base. This base conversion is a multiplication with - # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". - value = unsafe_div(x << 78, 5 ** 18) - - # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two - # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives - # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". - k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 - value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) - - # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, - # we will multiply by a scaling factor later. - y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) - p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ - 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) - - # We leave `p` in the "2 ** 192" base so that we do not have to scale it up - # again for the division. - q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) - q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) - q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) - - # The polynomial `q` has no zeros in the range because all its roots are complex. - # No scaling is required, as `p` is already "2 ** 96" too large. Also, - # `r` is in the range "(0.09, 0.25) * 2**96" after the division. - r: int256 = unsafe_div(p, q) - - # To finalise the calculation, we have to multiply `r` by: - # - the scale factor "s = ~6.031367120", - # - the factor "2 ** k" from the range reduction, and - # - the factor "1e18 / 2 ** 96" for the base conversion. - # We do this all at once, with an intermediate result in "2**213" base, - # so that the final right shift always gives a positive value. - - # Note that to circumvent Vyper's safecast feature for the potentially - # negative parameter value `r`, we first convert `r` to `bytes32` and - # subsequently to `uint256`. Remember that the EVM default behaviour is - # to use two's complement representation to handle signed integers. - return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) - # ---------------------------- ERC20 Utils ----------------------------------- @@ -1843,7 +1630,7 @@ def get_virtual_price() -> uint256: """ amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = self.get_D(xp, amp) + D: uint256 = math.get_D(xp, amp) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio return D * PRECISION / self.totalSupply diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy new file mode 100644 index 00000000..f96fd727 --- /dev/null +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -0,0 +1,245 @@ +# @version 0.3.9 +""" +@title CurveStableSwapNG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Math for StableSwapMetaNG implementation (since not enough bytecode space) +""" + +MAX_COINS: constant(uint256) = 8 +N_COINS: constant(uint256) = 2 +N_COINS_128: constant(int128) = 2 +A_PRECISION: constant(uint256) = 100 + + +@external +@pure +def get_y( + i: int128, + j: int128, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _D: uint256 +) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + amp: uint256 = _amp + D: uint256 = _D + # if _D == 0: # TODO: figure out why this even exists? + # amp = self._A() + # D = self.get_D(xp, amp) + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(N_COINS_128): + + if _i == i: + _x = x + elif _i != j: + _x = xp[_i] + else: + continue + + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@external +@pure +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + D_P: uint256 = 0 + Dprev: uint256 = 0 + + for i in range(255): + D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + Dprev = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@external +@pure +def get_y_D( + A: uint256, + i: int128, + xp: DynArray[uint256, MAX_COINS], + D: uint256 +) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(N_COINS_128): + + if _i != i: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@external +@pure +def exp(x: int256) -> uint256: + + """ + @dev Calculates the natural exponential function of a signed integer with + a precision of 1e18. + @notice Note that this function consumes about 810 gas units. The implementation + is inspired by Remco Bloemen's implementation under the MIT license here: + https://xn--2-umb.com/22/exp-ln. + @dev This implementation is derived from Snekmate, which is authored + by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. + https://github.com/pcaversaccio/snekmate + @param x The 32-byte variable. + @return int256 The 32-byte calculation result. + """ + value: int256 = x + + # If the result is `< 0.5`, we return zero. This happens when we have the following: + # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". + if (x <= -42139678854452767551): + return empty(uint256) + + # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. + # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". + assert x < 135305999368893231589, "wad_exp overflow" + + # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher + # intermediate precision and a binary base. This base conversion is a multiplication with + # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". + value = unsafe_div(x << 78, 5 ** 18) + + # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two + # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives + # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". + k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 + value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) + + # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, + # we will multiply by a scaling factor later. + y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) + p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ + 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) + + # We leave `p` in the "2 ** 192" base so that we do not have to scale it up + # again for the division. + q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) + q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) + q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) + + # The polynomial `q` has no zeros in the range because all its roots are complex. + # No scaling is required, as `p` is already "2 ** 96" too large. Also, + # `r` is in the range "(0.09, 0.25) * 2**96" after the division. + r: int256 = unsafe_div(p, q) + + # To finalise the calculation, we have to multiply `r` by: + # - the scale factor "s = ~6.031367120", + # - the factor "2 ** k" from the range reduction, and + # - the factor "1e18 / 2 ** 96" for the base conversion. + # We do this all at once, with an intermediate result in "2**213" base, + # so that the final right shift always gives a positive value. + + # Note that to circumvent Vyper's safecast feature for the potentially + # negative parameter value `r`, we first convert `r` to `bytes32` and + # subsequently to `uint256`. Remember that the EVM default behaviour is + # to use two's complement representation to handle signed integers. + return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) diff --git a/poetry.lock b/poetry.lock index cbe842a3..241955aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -354,6 +354,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + [[package]] name = "charset-normalizer" version = "3.1.0" @@ -1150,6 +1161,20 @@ pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] +[[package]] +name = "identify" +version = "2.5.24" +description = "File identification library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -1644,6 +1669,20 @@ files = [ {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "packaging" version = "23.1" @@ -1849,6 +1888,24 @@ files = [ poetry = ">=1.5.0,<2.0.0" poetry-core = ">=1.6.0,<2.0.0" +[[package]] +name = "pre-commit" +version = "3.3.3" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, + {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prompt-toolkit" version = "3.0.38" @@ -2204,6 +2261,55 @@ files = [ {file = "pywin32_ctypes-0.2.1-py3-none-any.whl", hash = "sha256:b9a53ef754c894a525469933ab2a447c74ec1ea6b9d2ef446f40ec50d3dcec9f"}, ] +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + [[package]] name = "rapidfuzz" version = "2.15.1" @@ -2519,6 +2625,22 @@ files = [ dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] doc = ["Sphinx", "sphinx-rtd-theme"] +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shellingham" version = "1.5.0.post1" @@ -2892,4 +3014,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b5db3375dfda42b1e0e448285142ec0666eae8f249d7539d1ee7943cf6e7629b" +content-hash = "1637c74047db6fa1232614ff15ff07f6bc9e5eebccb13c46e31d87da415e1eb7" diff --git a/pyproject.toml b/pyproject.toml index 05dfdc2e..9f26cd44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ python = "^3.10" poetry = "1.5.1" titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "bd4af917754e7a66a84dee6134c8a42112224471"} vyper = "^0.3.9" +pycryptodome = "^3.18.0" +pre-commit = "^3.3.3" [tool.poetry.group.dev.dependencies] black = "22.3.0" diff --git a/tests/conftest.py b/tests/conftest.py index 2176b0da..919445ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -54,7 +54,7 @@ def pytest_generate_tests(metafunc): pool_size = int(metafunc.config.getoption("pool_size")) # TODO: remove after adding implementations - assert pool_size == 2, "Only 2-coin pools supported" + # assert pool_size == 2, "Only 2-coin pools supported" if "pool_size" in metafunc.fixturenames: metafunc.parametrize( From aa5541bf6793e63d20475c3a89b4adb1539121b4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:55:46 +0200 Subject: [PATCH 097/337] incorporate math impl to meta impl --- .github/workflows/test_token.yaml | 9 +- contracts/main/CurveStableSwapFactoryNG.vy | 12 + contracts/main/CurveStableSwapMetaNG.vy | 283 +++++++++++---------- contracts/main/CurveStableSwapNG.vy | 4 +- contracts/main/CurveStableSwapNGMath.vy | 59 +++-- tests/fixtures/factory.py | 12 + 6 files changed, 211 insertions(+), 168 deletions(-) diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index 1a7a1dce..a120b2af 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -34,8 +34,7 @@ jobs: source .venv/bin/activate pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto - TODO: uncomment after meta fix - - name: Run Test Meta - run: | - source .venv/bin/activate - pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto + - name: Run Test Meta + run: | + source .venv/bin/activate + pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 86bc6a3e..19a772dc 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -96,6 +96,7 @@ base_pool_assets: public(HashMap[address, bool]) # index -> implementation address pool_implementations: public(HashMap[uint256, address]) metapool_implementations: public(HashMap[uint256, address]) +math_implementation: public(address) gauge_implementation: public(address) views_implementation: public(address) @@ -791,6 +792,17 @@ def set_metapool_implementations( self.metapool_implementations[_implementation_index] = _implementation +@external +def set_math_implementation(_math_implementation: address): + """ + @notice Set implementation contracts for StableSwap Math + @dev Only callable by admin + @param _implementation Implementation address to use when stableswap pools + """ + assert msg.sender == self.admin # dev: admin-only function + self.math_implementation = _math_implementation + + @external def set_gauge_implementation(_gauge_implementation: address): """ diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e411b048..e01939ac 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,15 +1,14 @@ # @version 0.3.9 """ -@title CurveStableSwapNG +@title CurveStableSwapMetaNG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice Stableswap implementation for up to 8 coins with no rehypothecation, - i.e. tokens are not deposited into other contracts. Supports only - token pairs that are similarly priced. This contract also - supports metapools (2-coin pools where the second coin is an LP token). - The Pool contract also records exponential moving averages for coins - 1, 2 and 3 relative to coin 0. -@dev Supports: +@notice Stableswap Metapool implementation for 2 coins with no rehypothecation, + i.e. tokens are not deposited into other contracts. Supports pegged assets. + Metapools are pools where the coin on index 1 is a liquidity pool token + of another pool. This exposes methods such as exchange_underlying, which + exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the + nth coin index of the base pool. @dev Asset Types: 0. Basic ERC20 token with no additional features 1. WETH - can we directly converted to/from ETH @@ -20,25 +19,22 @@ 2. ERC20 tokens can have arbitrary decimals (<=18). 3. ERC20 tokens that rebase (either positive or fee on transfer) 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) - Additional features include: - 1. Support for rebasing tokens: but this disables - `exchange_received`. - 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. - 3. Support for ETH/WETH transfers - 4. Adds oracles based on AMM State Price (and _not_ last traded price). - 5. Adds exchanging tokens with callbacks that allows for: + Additional features include: + 1. Adds oracles based on AMM State Price (and _not_ last traded price). + State prices are calculated _after_ liquidity operations, using bonding + curve math. + 2. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature: `exchange_received`, which is inspired - by Uniswap V2: swaps that expect an ERC20 transfer to have occurred + 3. Adds feature: `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 3 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 3 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. """ @@ -91,15 +87,21 @@ interface Math: x: uint256, xp: DynArray[uint256, MAX_COINS], _amp: uint256, - _D: uint256 + _D: uint256, + _n_coins: uint256 ) -> uint256: view def get_y_D( A: uint256, i: int128, xp: DynArray[uint256, MAX_COINS], - D: uint256 + D: uint256, + _n_coins: uint256 + ) -> uint256: view + def get_D( + _xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _n_coins: uint256 ) -> uint256: view - def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: view def exp(x: int256) -> uint256: view # --------------------------------- Events ----------------------------------- @@ -590,8 +592,8 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, + _use_eth: bool, + _receiver: address, ) -> uint256: """ @notice Perform an exchange between two coins without transferring token in @@ -628,6 +630,7 @@ def exchange_underlying( j: int128, _dx: uint256, _min_dy: uint256, + _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -663,6 +666,7 @@ def exchange_underlying_extended( j: int128, _dx: uint256, _min_dy: uint256, + _use_eth: bool, _receiver: address, _cb: bytes32 ) -> uint256: @@ -698,6 +702,7 @@ def exchange_underlying_received( j: int128, _dx: uint256, _min_dy: uint256, + _use_eth: bool, _receiver: address, ) -> uint256: """ @@ -801,7 +806,7 @@ def add_liquidity( new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = math.get_D(xp, amp) + D2: uint256 = math.get_D(xp, amp, N_COINS) mint_amount = total_supply * (D2 - D0) / D0 self.save_p(xp, amp, D2) @@ -982,8 +987,8 @@ def __exchange( ) -> uint256: amp: uint256 = self._A() - D: uint256 = math.get_D(_xp, amp) - y: uint256 = math.get_y(i, j, x, _xp, amp, D) + D: uint256 = math.get_D(_xp, amp, N_COINS) + y: uint256 = math.get_y(i, j, x, _xp, amp, D, N_COINS) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR @@ -1058,151 +1063,151 @@ def _exchange( return dy -# @internal -# def _exchange_underlying( -# sender: address, -# i: int128, -# j: int128, -# _dx: uint256, -# _min_dy: uint256, -# receiver: address, -# callbacker: address, -# callback_sig: bytes32, -# expect_optimistic_transfer: bool = False -# ) -> uint256: - -# assert BASE_POOL != empty(address) # dev: pool is not a metapool +@internal +def _exchange_underlying( + sender: address, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + receiver: address, + callbacker: address, + callback_sig: bytes32, + expect_optimistic_transfer: bool = False +) -> uint256: -# rates: DynArray[uint256, MAX_COINS] = self._stored_rates() -# old_balances: DynArray[uint256, MAX_COINS] = self._balances() -# xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + assert BASE_POOL != empty(address) # dev: pool is not a metapool -# dy: uint256 = 0 -# base_i: int128 = 0 -# base_j: int128 = 0 -# meta_i: int128 = 0 -# meta_j: int128 = 0 -# x: uint256 = 0 -# input_coin: address = empty(address) -# output_coin: address = empty(address) + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) -# if i == 0: -# input_coin = coins[0] -# else: -# base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts -# meta_i = 1 -# input_coin = BASE_COINS[base_i] -# if j == 0: -# output_coin = coins[0] -# else: -# base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts -# meta_j = 1 -# output_coin = BASE_COINS[base_j] + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + input_coin: address = empty(address) + output_coin: address = empty(address) + + if i == 0: + input_coin = coins[0] + else: + base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts + meta_i = 1 + input_coin = BASE_COINS[base_i] + if j == 0: + output_coin = coins[0] + else: + base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts + meta_j = 1 + output_coin = BASE_COINS[base_j] -# # --------------------------- Do Transfer in ----------------------------- + # --------------------------- Do Transfer in ----------------------------- -# dx_w_fee: uint256 = 0 + dx_w_fee: uint256 = 0 -# # for exchange_underlying, optimistic transfers need to be handled differently -# if expect_optimistic_transfer: + # for exchange_underlying, optimistic transfers need to be handled differently + if expect_optimistic_transfer: -# assert asset_types[i] != 3 # dev: rebasing coins not supported + assert asset_types[i] != 3 # dev: rebasing coins not supported -# # This branch is never reached for rebasing tokens -# if input_coin == BASE_COINS[base_i]: -# # we expect base_coin's balance to be 0. So swap whatever base_coin's -# # balance the pool has: -# dx_w_fee = ERC20(input_coin).balanceOf(self) -# else: -# dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] -# assert dx_w_fee == _dx -# self.stored_balances[meta_i] += dx_w_fee + # This branch is never reached for rebasing tokens + if input_coin == BASE_COINS[base_i]: + # we expect base_coin's balance to be 0. So swap whatever base_coin's + # balance the pool has: + dx_w_fee = ERC20(input_coin).balanceOf(self) + else: + dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] + assert dx_w_fee == _dx + self.stored_balances[meta_i] += dx_w_fee -# dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx + dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx -# else: + else: -# dx_w_fee = self._transfer_in( -# i, -# _dx, -# _min_dy, -# callbacker, -# callback_sig, -# sender, -# receiver, -# False, # use_eth = False -# False, # expect_optimistic_transfer = False -# ) + dx_w_fee = self._transfer_in( + i, + _dx, + _min_dy, + callbacker, + callback_sig, + sender, + receiver, + False, # use_eth = False + False, # expect_optimistic_transfer = False + ) -# # ------------------------------------------------------------------------ + # ------------------------------------------------------------------------ -# if i == 0 or j == 0: # meta swap + if i == 0 or j == 0: # meta swap -# if i == 0: + if i == 0: -# x = xp[i] + dx_w_fee * rates[i] / PRECISION + x = xp[i] + dx_w_fee * rates[i] / PRECISION -# else: + else: -# dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) -# x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION -# x += xp[MAX_METAPOOL_COIN_INDEX] + dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) + x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + x += xp[MAX_METAPOOL_COIN_INDEX] -# dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) + dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) -# # Withdraw from the base pool if needed -# if j > 0: -# out_amount: uint256 = ERC20(output_coin).balanceOf(self) -# StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) -# dy = ERC20(output_coin).balanceOf(self) - out_amount + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount -# assert dy >= _min_dy + assert dy >= _min_dy -# # Adjust stored balances: -# self.stored_balances[meta_j] -= dy + # Adjust stored balances: + self.stored_balances[meta_j] -= dy -# else: # base pool swap (user should swap at base pool for better gas) + else: # base pool swap (user should swap at base pool for better gas) -# dy = ERC20(output_coin).balanceOf(self) -# StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) -# dy = ERC20(output_coin).balanceOf(self) - dy + dy = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy -# # --------------------------- Do Transfer out ---------------------------- + # --------------------------- Do Transfer out ---------------------------- -# assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) + assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) -# # ------------------------------------------------------------------------ + # ------------------------------------------------------------------------ -# log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! + log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! -# return dy + return dy -# @internal -# def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: +@internal +def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: -# coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] -# x: uint256 = ERC20(coin_i).balanceOf(self) + coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] + x: uint256 = ERC20(coin_i).balanceOf(self) -# if BASE_N_COINS == 2: + if BASE_N_COINS == 2: -# base_inputs: uint256[2] = empty(uint256[2]) -# base_inputs[base_i] = dx -# StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) + base_inputs: uint256[2] = empty(uint256[2]) + base_inputs[base_i] = dx + StableSwap2(BASE_POOL).add_liquidity(base_inputs, 0) -# if BASE_N_COINS == 3: + if BASE_N_COINS == 3: -# base_inputs: uint256[3] = empty(uint256[3]) -# base_inputs[base_i] = dx -# StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) + base_inputs: uint256[3] = empty(uint256[3]) + base_inputs[base_i] = dx + StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) -# else: + else: -# base_inputs: uint256[4] = empty(uint256[4]) -# base_inputs[base_i] = dx -# StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) + base_inputs: uint256[4] = empty(uint256[4]) + base_inputs[base_i] = dx + StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) -# return ERC20(coin_i).balanceOf(self) - x + return ERC20(coin_i).balanceOf(self) - x @internal @@ -1269,7 +1274,7 @@ def get_D_mem( _amp: uint256 ) -> uint256: xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) - return math.get_D(xp, _amp) + return math.get_D(xp, _amp, N_COINS) @view @@ -1288,11 +1293,11 @@ def _calc_withdraw_one_coin( amp: uint256 = self._A() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) - D0: uint256 = math.get_D(xp, amp) + D0: uint256 = math.get_D(xp, amp, N_COINS) total_supply: uint256 = self.totalSupply D1: uint256 = D0 - _burn_amount * D0 / total_supply - new_y: uint256 = math.get_y_D(amp, i, xp, D1) + new_y: uint256 = math.get_y_D(amp, i, xp, D1, N_COINS) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -1307,7 +1312,7 @@ def _calc_withdraw_one_coin( dx_expected = xp_j - xp_j * D1 / D0 xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR - dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1) + dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors @@ -1416,7 +1421,7 @@ def get_p(i: uint256) -> uint256: xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() ) - D: uint256 = math.get_D(xp, amp) + D: uint256 = math.get_D(xp, amp, N_COINS) return self._get_p(xp, amp, D)[0] @@ -1630,7 +1635,7 @@ def get_virtual_price() -> uint256: """ amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = math.get_D(xp, amp) + D: uint256 = math.get_D(xp, amp, N_COINS) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio return D * PRECISION / self.totalSupply diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index c9d5ca06..06f9b667 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -586,8 +586,8 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, - _receiver: address = msg.sender, + _use_eth: bool, + _receiver: address, ) -> uint256: """ @notice Perform an exchange between two coins without transferring token in diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index f96fd727..424d545c 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,14 +1,13 @@ # @version 0.3.9 """ -@title CurveStableSwapNG +@title CurveStableSwapNGMath @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice Math for StableSwapMetaNG implementation (since not enough bytecode space) +@notice Math for StableSwapMetaNG implementation """ MAX_COINS: constant(uint256) = 8 -N_COINS: constant(uint256) = 2 -N_COINS_128: constant(int128) = 2 +MAX_COINS_128: constant(int128) = 8 A_PRECISION: constant(uint256) = 100 @@ -20,7 +19,8 @@ def get_y( x: uint256, xp: DynArray[uint256, MAX_COINS], _amp: uint256, - _D: uint256 + _D: uint256, + _n_coins: uint256 ) -> uint256: """ Calculate x[j] if one makes x[i] = x @@ -33,26 +33,31 @@ def get_y( """ # x in the input is converted to the same price/precision + n_coins_128: int128 = convert(_n_coins, int128) + assert i != j # dev: same coin assert j >= 0 # dev: j below zero - assert j < N_COINS_128 # dev: j above N_COINS + assert j < n_coins_128 # dev: j above N_COINS # should be unreachable, but good for safety assert i >= 0 - assert i < N_COINS_128 + assert i < n_coins_128 amp: uint256 = _amp D: uint256 = _D - # if _D == 0: # TODO: figure out why this even exists? + # if _D == 0: # TODO: why this even exists? # amp = self._A() # D = self.get_D(xp, amp) S_: uint256 = 0 _x: uint256 = 0 y_prev: uint256 = 0 c: uint256 = D - Ann: uint256 = amp * N_COINS + Ann: uint256 = amp * _n_coins + + for _i in range(MAX_COINS_128): - for _i in range(N_COINS_128): + if _i == n_coins_128: + break if _i == i: _x = x @@ -62,9 +67,9 @@ def get_y( continue S_ += _x - c = c * D / (_x * N_COINS) + c = c * D / (_x * _n_coins) - c = c * D * A_PRECISION / (Ann * N_COINS) + c = c * D * A_PRECISION / (Ann * _n_coins) b: uint256 = S_ + D * A_PRECISION / Ann # - D y: uint256 = D @@ -83,7 +88,11 @@ def get_y( @external @pure -def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: +def get_D( + _xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _n_coins: uint256 +) -> uint256: """ D invariant calculation in non-overflowing integer operations iteratively @@ -100,14 +109,14 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: return 0 D: uint256 = S - Ann: uint256 = _amp * N_COINS + Ann: uint256 = _amp * _n_coins D_P: uint256 = 0 Dprev: uint256 = 0 for i in range(255): - D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(_n_coins, _n_coins) Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + D = (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) # Equality with the precision of 1 if D > Dprev: if D - Dprev <= 1: @@ -126,7 +135,8 @@ def get_y_D( A: uint256, i: int128, xp: DynArray[uint256, MAX_COINS], - D: uint256 + D: uint256, + _n_coins: uint256 ) -> uint256: """ Calculate x[i] if one reduces D from being calculated for xp to D @@ -139,25 +149,30 @@ def get_y_D( """ # x in the input is converted to the same price/precision + n_coins_128: int128 = convert(_n_coins, int128) + assert i >= 0 # dev: i below zero - assert i < N_COINS_128 # dev: i above N_COINS + assert i < n_coins_128 # dev: i above N_COINS S_: uint256 = 0 _x: uint256 = 0 y_prev: uint256 = 0 c: uint256 = D - Ann: uint256 = A * N_COINS + Ann: uint256 = A * _n_coins + + for _i in range(MAX_COINS_128): - for _i in range(N_COINS_128): + if _i == n_coins_128: + break if _i != i: _x = xp[_i] else: continue S_ += _x - c = c * D / (_x * N_COINS) + c = c * D / (_x * _n_coins) - c = c * D * A_PRECISION / (Ann * N_COINS) + c = c * D * A_PRECISION / (Ann * _n_coins) b: uint256 = S_ + D * A_PRECISION / Ann y: uint256 = D diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 4f5a63b5..8eaa6f6a 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -41,6 +41,12 @@ def views_implementation(deployer): return boa.load("contracts/main/CurveStableSwapNGViews.vy") +@pytest.fixture(scope="module") +def math_implementation(deployer): + with boa.env.prank(deployer): + return boa.load("contracts/main/CurveStableSwapNGMath.vy") + + @pytest.fixture(scope="module") def factory( deployer, @@ -101,6 +107,12 @@ def set_views_implementation(owner, factory, views_implementation): factory.set_views_implementation(views_implementation.address) +@pytest.fixture(scope="module") +def set_math_implementation(owner, factory, math_implementation): + with boa.env.prank(owner): + factory.set_math_implementation(math_implementation.address) + + @pytest.fixture(scope="module") def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): with boa.env.prank(owner): From 0e44cf1d59452027ad65b8d1a900ae7d5c8fb82f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:58:32 +0200 Subject: [PATCH 098/337] add math impl to deploy process --- contracts/main/CurveStableSwapFactoryNG.vy | 1 + contracts/main/CurveStableSwapMetaNG.vy | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 19a772dc..6784c981 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -653,6 +653,7 @@ def deploy_metapool( _A, # _A: uint256 _fee, # _fee: uint256 _ma_exp_time, # _ma_exp_time: uint256 + self.math_implementation, # _math_implementation: address WETH20, # _weth: address _base_pool, # _base_pool: address _coins, # _coins: DynArray[address, MAX_COINS] diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e01939ac..239c8b90 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -50,10 +50,6 @@ interface Factory: def admin() -> address: view def views_implementation() -> address: view -interface WETH: - def deposit(): payable - def withdraw(_amount: uint256): nonpayable - interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view From 445887873bc442010efd76f5ad955c7ca22966e8 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:34:53 +0200 Subject: [PATCH 099/337] fix add liquidity --- contracts/main/CurveStableSwapNG.vy | 4 +-- tests/conftest.py | 3 ++ tests/fixtures/tokens.py | 5 ++-- tests/pools/basic/test_liquidity.py | 43 ++++++++++++++++++++++++----- tests/utils/tokens.py | 19 +++++++------ 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index c9d5ca06..a1d3bb5f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -698,7 +698,7 @@ def add_liquidity( else: difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR + fees.append(base_fee * difference / FEE_DENOMINATOR) self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR new_balances[i] -= fees[i] @@ -1299,7 +1299,7 @@ def _get_p( if i == N_COINS: break - p[i - 1] = 10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr) + p.append(10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr)) return p diff --git a/tests/conftest.py b/tests/conftest.py index 2176b0da..f64eaa0e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -76,6 +76,9 @@ def pytest_generate_tests(metafunc): if "pool_token_types" in metafunc.fixturenames: cli_options = metafunc.config.getoption("token_types").split(",") + if "eth" in cli_options: + cli_options.remove("eth") + cli_options = ["eth"] + cli_options if pool_types[pool_type] == 0: combs = list(combinations(cli_options, pool_size)) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 19601977..5b661663 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -47,7 +47,7 @@ def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): for i, d in enumerate(decimals): - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], i != 0)) + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], i == 0)) return tokens @@ -58,8 +58,7 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke if t == 0: pool_tokens.append(plain_tokens[i]) elif t == 1: - # Enforce eth as 0th token - pool_tokens = [weth] + pool_tokens + pool_tokens.append(weth) elif t == 2: pool_tokens.append(oracle_tokens[i]) elif t == 3: diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 95c7ebae..b29c485d 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -11,19 +11,48 @@ class TestLiquidityMethods: class TestAddLiquidity: @pytest.mark.parametrize("use_eth", (True, False), scope="session") def test_add_liquidity( - self, bob, swap, is_eth_pool, pool_tokens, deposit_amounts, initial_balance, initial_amounts, use_eth + self, + bob, + swap, + is_eth_pool, + pool_tokens, + deposit_amounts, + initial_balance, + initial_amounts, + use_eth, + pool_token_types, ): - value = deposit_amounts[0] if is_eth_pool and use_eth else 0 - deposit_amounts[0] = 0 if is_eth_pool and use_eth else deposit_amounts[0] + value = deposit_amounts[0] if (is_eth_pool and use_eth) else 0 swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=value) + is_ideal = True for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): - assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + if pool_token_types[i] == 0 or pool_token_types[i] == 2 or (pool_tokens[i] == 1 and not use_eth): + assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + + if pool_token_types[i] == 2: + is_ideal = False + + elif pool_token_types[i] == 1 and use_eth: + assert boa.env.get_balance(bob) == initial_balance - value + assert pool_token.balanceOf(bob) == initial_amounts[i] + assert boa.env.get_balance(swap.address) == 0 + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + + elif pool_token_types[i] == 3: + is_ideal = False + if i == 0: # up rebasing + assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 + else: # down rebasing + assert pool_token.balanceOf(bob) <= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] * 2 ideal = len(pool_tokens) * DEPOSIT_AMOUNT * 10**18 - assert abs(swap.balanceOf(bob) - ideal) <= 1 - assert abs(swap.totalSupply() - ideal * 2) <= 2 + if is_ideal: + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 @pytest.mark.parametrize("idx", (0, 1)) @pytest.mark.parametrize("use_eth", (True, False)) diff --git a/tests/utils/tokens.py b/tests/utils/tokens.py index 46bc0c67..e89880be 100644 --- a/tests/utils/tokens.py +++ b/tests/utils/tokens.py @@ -9,12 +9,15 @@ def mint_for_testing(user: str, amount, token_contract: VyperContract | None, mi user = to_checksum_address(user) if mint_eth: - boa.env.set_balance(user, boa.env.get_balance(user) + amount) + boa.env.set_balance(user, amount) else: - if token_contract.symbol() == "WETH": - boa.env.set_balance(user, boa.env.get_balance(user) + amount) - with boa.env.prank(user): - token_contract.deposit(value=amount) - else: - with boa.env.prank(user): - token_contract._mint_for_testing(user, amount) + balance = token_contract.balanceOf(user) + if balance < amount: + _amount_to_add = amount - balance + if token_contract.symbol() == "WETH": + boa.env.set_balance(user, boa.env.get_balance(user) + _amount_to_add) + with boa.env.prank(user): + token_contract.deposit(value=_amount_to_add) + else: + with boa.env.prank(user): + token_contract._mint_for_testing(user, _amount_to_add) From 33292e55c9cbebf1f7856a31a36a0c5af6bef74f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:08:18 +0200 Subject: [PATCH 100/337] safe unsafe div for constants --- contracts/main/CurveStableSwapFactoryNG.vy | 1 - contracts/main/CurveStableSwapMetaNG.vy | 44 +++++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 6784c981..1b1afdb5 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -76,7 +76,6 @@ event LiquidityGaugeDeployed: WETH20: public(immutable(address)) MAX_COINS: constant(uint256) = 8 -MAX_METAPOOL_COINS: constant(uint256) = 2 ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 admin: public(address) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 239c8b90..f506470b 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -486,7 +486,9 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: ) assert len(response) != 0 - rates[i] = rates[i] * convert(response, uint256) / PRECISION + + # rates[i] * convert(response, uint256) / PRECISION + rates[i] = unsafe_div(rates[i] * convert(response, uint256), PRECISION) return rates @@ -797,8 +799,11 @@ def add_liquidity( else: difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + # base_fee * difference / FEE_DENOMINATOR + fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) + + # fees[i] * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fees[i] * ADMIN_FEE, FEE_DENOMINATOR) new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) @@ -846,7 +851,8 @@ def remove_liquidity_one_coin( dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" - self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR + # fee * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fee * ADMIN_FEE, FEE_DENOMINATOR) self._burnFrom(msg.sender, _burn_amount) @@ -902,8 +908,12 @@ def remove_liquidity_imbalance( else: difference = new_balance - ideal_balance - fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + # base_fee * difference / FEE_DENOMINATOR + fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) + + # fees[i] * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fees[i] * ADMIN_FEE, FEE_DENOMINATOR) + new_balances[i] -= fees[i] D2: uint256 = self.get_D_mem(rates, new_balances, amp) @@ -993,7 +1003,7 @@ def __exchange( dy = (dy - dy_fee) * PRECISION / rates[j] self.admin_balances[j] += ( - dy_fee * ADMIN_FEE / FEE_DENOMINATOR + unsafe_div(dy_fee * ADMIN_FEE, FEE_DENOMINATOR) # dy_fee * ADMIN_FEE / FEE_DENOMINATOR ) * PRECISION / rates[j] # Calculate and store state prices: @@ -1044,7 +1054,8 @@ def _exchange( # ------------------------------- Exchange ------------------------------- - x: uint256 = xp[i] + dx * rates[i] / PRECISION + # xp[i] + dx * rates[i] / PRECISION + x: uint256 = xp[i] + unsafe_div(dx * rates[i], PRECISION) dy: uint256 = self.__exchange(dx, x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" @@ -1141,12 +1152,15 @@ def _exchange_underlying( if i == 0: - x = xp[i] + dx_w_fee * rates[i] / PRECISION + # xp[i] + dx_w_fee * rates[i] / PRECISION + x = xp[i] + unsafe_div(dx_w_fee * rates[i], PRECISION) else: dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - x = dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + + # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) x += xp[MAX_METAPOOL_COIN_INDEX] dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) @@ -1257,7 +1271,8 @@ def _xp_mem( result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(N_COINS_128): - result[i] = _rates[i] * _balances[i] / PRECISION + # _rates[i] * _balances[i] / PRECISION + result[i] = unsafe_div(_rates[i] * _balances[i], PRECISION) return result @@ -1306,7 +1321,9 @@ def _calc_withdraw_one_coin( dx_expected = xp_j * D1 / D0 - new_y else: dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + + # xp_j - base_fee * dx_expected / FEE_DENOMINATOR + xp_reduced[j] = xp_j - unsafe_div(base_fee * dx_expected, FEE_DENOMINATOR) dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees @@ -1346,7 +1363,8 @@ def _get_p( Dr = Dr * D / xp[i] p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - xp0_A: uint256 = ANN * xp[0] / A_PRECISION + # ANN * xp[0] / A_PRECISION + xp0_A: uint256 = unsafe_div(ANN * xp[0], A_PRECISION) p.append(10**18 * (xp0_A + Dr * xp[0] / xp[1]) / (xp0_A + Dr)) return p From fc0deaaa91590cc72ef38ca361a61cfcecaf7ec4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 15 Jul 2023 12:53:30 +0200 Subject: [PATCH 101/337] remove stableswap4 for base pools --- contracts/main/CurveStableSwapMetaNG.vy | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index f506470b..771c8ecb 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -3,13 +3,12 @@ @title CurveStableSwapMetaNG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice Stableswap Metapool implementation for 2 coins with no rehypothecation, - i.e. tokens are not deposited into other contracts. Supports pegged assets. - Metapools are pools where the coin on index 1 is a liquidity pool token - of another pool. This exposes methods such as exchange_underlying, which - exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the - nth coin index of the base pool. -@dev Asset Types: +@notice Stableswap Metapool implementation for 2 coins. Supports pegged assets. +@dev Metapools are pools where the coin on index 1 is a liquidity pool token + of another pool. This exposes methods such as exchange_underlying, which + exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the + nth coin index of the base pool. + Asset Types: 0. Basic ERC20 token with no additional features 1. WETH - can we directly converted to/from ETH 2. Oracle - token with rate oracle @@ -68,9 +67,6 @@ interface StableSwap2: interface StableSwap3: def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable -interface StableSwap4: - def add_liquidity(amounts: uint256[4], min_mint_amount: uint256): nonpayable - interface StableSwap: def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable @@ -1211,12 +1207,6 @@ def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: base_inputs[base_i] = dx StableSwap3(BASE_POOL).add_liquidity(base_inputs, 0) - else: - - base_inputs: uint256[4] = empty(uint256[4]) - base_inputs[base_i] = dx - StableSwap4(BASE_POOL).add_liquidity(base_inputs, 0) - return ERC20(coin_i).balanceOf(self) - x From 63f70c9be0604a3ebd87dd303a636e5b342770e8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 15 Jul 2023 13:34:36 +0200 Subject: [PATCH 102/337] check correct asset_type index --- contracts/main/CurveStableSwapMetaNG.vy | 18 ++++++++++-------- contracts/main/CurveStableSwapNG.vy | 19 +++++++++++++------ contracts/main/CurveStableSwapNGMath.vy | 18 +++++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 771c8ecb..227c3fbc 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -384,6 +384,10 @@ def _transfer_in( # ------------------------- Handle Transfers ----------------------------- + if input_coin == BASE_COINS[base_i]: + + return + if expect_optimistic_transfer: assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" @@ -1114,14 +1118,16 @@ def _exchange_underlying( # for exchange_underlying, optimistic transfers need to be handled differently if expect_optimistic_transfer: - assert asset_types[i] != 3 # dev: rebasing coins not supported - - # This branch is never reached for rebasing tokens if input_coin == BASE_COINS[base_i]: + + assert asset_types[base_i + 2] != 3 # dev: rebasing coins not supported # we expect base_coin's balance to be 0. So swap whatever base_coin's # balance the pool has: dx_w_fee = ERC20(input_coin).balanceOf(self) else: + + assert asset_types[i] != 3 # dev: rebasing coins not supported + dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] assert dx_w_fee == _dx self.stored_balances[meta_i] += dx_w_fee @@ -1142,7 +1148,7 @@ def _exchange_underlying( False, # expect_optimistic_transfer = False ) - # ------------------------------------------------------------------------ + # ------------------------------- Exchange ------------------------------- if i == 0 or j == 0: # meta swap @@ -1436,10 +1442,6 @@ def price_oracle(i: uint256) -> uint256: return self._ma_price() -# ----------------------------- Math Utils ----------------------------------- - - - # ---------------------------- ERC20 Utils ----------------------------------- diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 06f9b667..34645d58 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -137,10 +137,6 @@ event ApplyNewFee: MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 -MAX_METAPOOL_COIN_INDEX: constant(int128) = 1 - -# Implementation does not impose transfer restrictions: -PERMISSIONED: public(constant(bool)) = False # ---------------------------- Pool Variables -------------------------------- @@ -1088,11 +1084,22 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: Ann: uint256 = _amp * N_COINS D_P: uint256 = 0 Dprev: uint256 = 0 + N_pow_N: uint256 = pow_mod256(N_COINS, N_COINS) for i in range(255): - D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) + + # D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS + D_P = unsafe_div(D * D / _xp[0] * D / _xp[1], N_pow_N) Dprev = D - D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + + # (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) + D = ( + (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * + D / ( + unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + + unsafe_add(N_COINS, 1) * D_P + ) + ) # Equality with the precision of 1 if D > Dprev: if D - Dprev <= 1: diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 424d545c..22b364f5 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -45,9 +45,6 @@ def get_y( amp: uint256 = _amp D: uint256 = _D - # if _D == 0: # TODO: why this even exists? - # amp = self._A() - # D = self.get_D(xp, amp) S_: uint256 = 0 _x: uint256 = 0 y_prev: uint256 = 0 @@ -112,11 +109,22 @@ def get_D( Ann: uint256 = _amp * _n_coins D_P: uint256 = 0 Dprev: uint256 = 0 + N_pow_N: uint256 = pow_mod256(_n_coins, _n_coins) for i in range(255): - D_P = D * D / _xp[0] * D / _xp[1] / pow_mod256(_n_coins, _n_coins) + + # D * D / _xp[0] * D / _xp[1] / _n_coins**_n_coins + D_P = unsafe_div(D * D / _xp[0] * D / _xp[1], N_pow_N) Dprev = D - D = (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) + + # (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) + D = ( + (unsafe_div(Ann * S, A_PRECISION) + D_P * _n_coins) * + D / ( + unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + + unsafe_add(_n_coins, 1) * D_P + ) + ) # Equality with the precision of 1 if D > Dprev: if D - Dprev <= 1: From 3bcf398d2b2a8fd6e1a600adc76ac6a44d4b6ddf Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 15 Jul 2023 13:40:58 +0200 Subject: [PATCH 103/337] remove unused code --- contracts/main/CurveStableSwapMetaNG.vy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 227c3fbc..d72827b9 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -384,10 +384,6 @@ def _transfer_in( # ------------------------- Handle Transfers ----------------------------- - if input_coin == BASE_COINS[base_i]: - - return - if expect_optimistic_transfer: assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" @@ -1121,9 +1117,11 @@ def _exchange_underlying( if input_coin == BASE_COINS[base_i]: assert asset_types[base_i + 2] != 3 # dev: rebasing coins not supported + # we expect base_coin's balance to be 0. So swap whatever base_coin's # balance the pool has: dx_w_fee = ERC20(input_coin).balanceOf(self) + else: assert asset_types[i] != 3 # dev: rebasing coins not supported From 69f589c3209488c55101fbad9645aad15703312c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 15 Jul 2023 14:26:40 +0200 Subject: [PATCH 104/337] metapools working --- contracts/main/CurveStableSwapFactoryNG.vy | 1 - tests/fixtures/factory.py | 11 ++++++++++- tests/fixtures/pools.py | 22 +++++++++++----------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 1b1afdb5..e6c9d8ad 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -653,7 +653,6 @@ def deploy_metapool( _fee, # _fee: uint256 _ma_exp_time, # _ma_exp_time: uint256 self.math_implementation, # _math_implementation: address - WETH20, # _weth: address _base_pool, # _base_pool: address _coins, # _coins: DynArray[address, MAX_COINS] self.base_pool_data[_base_pool].coins, # base_coins: DynArray[address, MAX_COINS] diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 8eaa6f6a..d25e27a0 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -53,6 +53,9 @@ def factory( fee_receiver, owner, weth, + gauge_implementation, + views_implementation, + math_implementation, ): with boa.env.prank(deployer): _factory = boa.load( @@ -61,6 +64,12 @@ def factory( owner, weth, ) + + with boa.env.prank(owner): + _factory.set_gauge_implementation(gauge_implementation.address) + _factory.set_views_implementation(views_implementation.address) + _factory.set_math_implementation(math_implementation.address) + return _factory @@ -74,7 +83,7 @@ def set_pool_implementations(owner, factory, amm_implementation): @pytest.fixture(scope="module") def set_metapool_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): - factory.set_pool_implementations(0, amm_implementation_meta.address) + factory.set_metapool_implementations(0, amm_implementation_meta.address) @pytest.fixture(scope="module") diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 2041f260..3eede56d 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -88,17 +88,17 @@ def swap( asset_type = 3 pool = factory.deploy_metapool( - base_pool.address, - "test", - "test", - underlying_tokens[0].address, - A, - fee, - 866, - 0, - asset_type, - method_id, - oracle, + base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + underlying_tokens[0].address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 = 0, + asset_type, # _asset_type: uint8 = 0, + method_id, # _method_id: bytes4 = empty(bytes4), + oracle, # _oracle: address = empty(address), ) return amm_interface_meta.at(pool) From 16bd758fc4ef3145ae767e9e0bad568f799d93b1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:00:47 +0200 Subject: [PATCH 105/337] add methods to callbackswap contract --- contracts/mocks/CallbackSwap.vy | 59 +++++++++++++++++++++++++-- tests/pools/test_exchange_received.py | 0 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/pools/test_exchange_received.py diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index edfc8960..b02dfe5d 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -12,9 +12,10 @@ from vyper.interfaces import ERC20 interface Swap: + def coins(i: uint256) -> address: view def exchange_extended( - i: uint256, - j: uint256, + i: int128, + j: int128, dx: uint256, min_dy: uint256, use_eth: bool, @@ -22,6 +23,22 @@ interface Swap: receiver: address, cb: bytes32 ) -> uint256: nonpayable + def exchange_received( + i: int128, + j: int128, + dx: uint256, + min_dy: uint256, + use_eth: bool, + receiver: address, + ) -> uint256: nonpayable + def exchange_underlying_received( + i: int128, + j: int128, + dx: uint256, + min_dy: uint256, + use_eth: bool, + receiver: address, + ) -> uint256: nonpayable vault: public(immutable(address)) @@ -84,8 +101,8 @@ def transfer_callback( @external def callback_and_swap( - i: uint256, - j: uint256, + i: int128, + j: int128, dx: uint256, min_dy: uint256, ) -> uint256: @@ -111,3 +128,37 @@ def callback_and_swap( vault, # receiver convert(selector, bytes32) # <-- your callback is being called here ) + + +@external +def transfer_and_swap( + i: int128, + j: int128, + dx: uint256, + min_dy: uint256, + underlying: bool +) -> uint256: + + assert msg.sender == keeper + + coin: address = whitelisted_pool.coins(convert(i, uint256)) + ERC20(coin).transferFrom(vault, whitelisted_pool.address, dx) + + if not underlying: + return whitelisted_pool.exchange_received( + i, # input coin index + j, # output coin index + dx, # amount in + min_dy, # minimum expected out + False, # use native token (eth) + vault, # receiver + ) + + return whitelisted_pool.exchange_underlying_received( + i, # input coin index + j, # output coin index + dx, # amount in + min_dy, # minimum expected out + False, # use native token (eth) + vault, # receiver + ) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py new file mode 100644 index 00000000..e69de29b From 904dedb90a51c834863fcddee6f7a7728e736a47 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:07:51 +0200 Subject: [PATCH 106/337] start writing tests for optimistic swaps --- contracts/main/CurveStableSwapNGViews.vy | 2 - contracts/mocks/CallbackSwap.vy | 17 +++---- tests/pools/test_exchange_received.py | 0 tests/pools/test_optimistic_swap.py | 56 ++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 12 deletions(-) delete mode 100644 tests/pools/test_exchange_received.py create mode 100644 tests/pools/test_optimistic_swap.py diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 7061a3d9..03bb9ec4 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -26,8 +26,6 @@ interface StableSwap2: interface StableSwap3: def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view -# TODO: Add up until 7 (a basepool can have maximally 7 if 8 is MAX_COINS. the other 1 is the non base pool token.) - A_PRECISION: constant(uint256) = 100 MAX_COINS: constant(uint256) = 8 diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index b02dfe5d..9a21156d 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -1,7 +1,7 @@ # @version 0.3.9 """ -@title CurveExchangeExtendedDemo +@title CurveExchangeWithoutApproval @author fiddyresearch.eth @notice A demo of a strategy execution that swaps on Curve pools without granting an ERC20 approvals to the DEX contracts @@ -41,19 +41,16 @@ interface Swap: ) -> uint256: nonpayable -vault: public(immutable(address)) keeper: public(immutable(address)) whitelisted_pool: public(immutable(Swap)) @external def __init__( - _vault: address, _whitelisted_pool: address, _keeper: address ): - vault = _vault whitelisted_pool = Swap(_whitelisted_pool) keeper = _keeper @@ -67,7 +64,7 @@ def transfer_callback( amount_to_receive: uint256, ): """ - Curve CryptoSwap (factory only) pools expect the callback to have the inputs: + Curve StableswapNG (factory only) pools expect the callback to have the inputs: sender: address receiver: address coin: address @@ -96,7 +93,7 @@ def transfer_callback( assert msg.sender == whitelisted_pool.address assert tx.origin == keeper - ERC20(coin).transferFrom(vault, whitelisted_pool.address, amount_to_transfer) + ERC20(coin).transferFrom(keeper, whitelisted_pool.address, amount_to_transfer) @external @@ -125,7 +122,7 @@ def callback_and_swap( min_dy, # minimum expected out False, # use native token (eth) msg.sender, # sender (doesnt matter because we set it to the vault in the callback) - vault, # receiver + keeper, # receiver convert(selector, bytes32) # <-- your callback is being called here ) @@ -142,7 +139,7 @@ def transfer_and_swap( assert msg.sender == keeper coin: address = whitelisted_pool.coins(convert(i, uint256)) - ERC20(coin).transferFrom(vault, whitelisted_pool.address, dx) + ERC20(coin).transferFrom(keeper, whitelisted_pool.address, dx) if not underlying: return whitelisted_pool.exchange_received( @@ -151,7 +148,7 @@ def transfer_and_swap( dx, # amount in min_dy, # minimum expected out False, # use native token (eth) - vault, # receiver + keeper, # receiver ) return whitelisted_pool.exchange_underlying_received( @@ -160,5 +157,5 @@ def transfer_and_swap( dx, # amount in min_dy, # minimum expected out False, # use native token (eth) - vault, # receiver + keeper, # receiver ) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/test_optimistic_swap.py b/tests/pools/test_optimistic_swap.py new file mode 100644 index 00000000..868aeb7b --- /dev/null +++ b/tests/pools/test_optimistic_swap.py @@ -0,0 +1,56 @@ +import boa +import pytest + +# from tests.utils.transactions import call_returning_result_and_logs + +SWAP_AMOUNT = 500_000 + + +class TestOptimisticSwap: + @pytest.fixture(scope="module") + def callback_contract(self, bob, swap, mint_bob, pool_tokens): + + with boa.env.prank(bob): + _callback = boa.load("contracts/mocks/CallbackSwap.vy", swap.address, bob) + for token in pool_tokens: + token.approve(_callback.address, 2**256 - 1) + + return _callback + + @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") + class TestExchangeReceived: + + # TODO: need to permutate/combinate N_COIN combos. + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_exchange_received(self, bob, swap, callback_contract, pool_tokens, sending, receiving, zero_address): + + coin = pool_tokens[sending] + assert coin.address == swap.coins(sending) + amount_in = SWAP_AMOUNT * 10 ** (coin.decimals()) + underlying = False + + bob_sending_balance_before = pool_tokens[sending].balanceOf(bob) + bob_receiving_balance_before = pool_tokens[receiving].balanceOf(bob) + pool_sending_balance_before = pool_tokens[sending].balanceOf(swap.address) + pool_receiving_balance_before = pool_tokens[receiving].balanceOf(swap.address) + + with boa.env.prank(bob): + amount_out = callback_contract.transfer_and_swap(sending, receiving, amount_in, 0, underlying) + assert amount_out > 0 + + bob_sending_balance_after = pool_tokens[sending].balanceOf(bob) + bob_receiving_balance_after = pool_tokens[receiving].balanceOf(bob) + pool_sending_balance_after = pool_tokens[sending].balanceOf(swap.address) + pool_receiving_balance_after = pool_tokens[receiving].balanceOf(swap.address) + + # TODO: incorporate cases for different asset types: + assert bob_sending_balance_before - bob_sending_balance_after == amount_in + assert bob_receiving_balance_after - bob_receiving_balance_before == amount_out + + assert pool_sending_balance_after - pool_sending_balance_before == amount_in + assert pool_receiving_balance_before - pool_receiving_balance_after == amount_out + + # TODO: Add tests for exchange_underlying_received + # @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") + # class TestExchangeUnderlyingReceived: + # pass From 9c0fe3a7d923e6ee5d50e259998daf9223854df4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:44:47 +0200 Subject: [PATCH 107/337] test_exchange_received_nonrebasing works but it somehow includes rebasing tokens --- tests/pools/test_optimistic_swap.py | 59 ++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/tests/pools/test_optimistic_swap.py b/tests/pools/test_optimistic_swap.py index 868aeb7b..09226e1d 100644 --- a/tests/pools/test_optimistic_swap.py +++ b/tests/pools/test_optimistic_swap.py @@ -17,17 +17,14 @@ def callback_contract(self, bob, swap, mint_bob, pool_tokens): return _callback - @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") - class TestExchangeReceived: - - # TODO: need to permutate/combinate N_COIN combos. - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_exchange_received(self, bob, swap, callback_contract, pool_tokens, sending, receiving, zero_address): + @pytest.fixture(scope="module") + def transfer_and_swap(self, callback_contract, bob): + def _transfer_and_swap(swap, pool_tokens, sending, receiving, underlying): coin = pool_tokens[sending] - assert coin.address == swap.coins(sending) amount_in = SWAP_AMOUNT * 10 ** (coin.decimals()) - underlying = False + + assert coin.address == swap.coins(sending) bob_sending_balance_before = pool_tokens[sending].balanceOf(bob) bob_receiving_balance_before = pool_tokens[receiving].balanceOf(bob) @@ -43,14 +40,42 @@ def test_exchange_received(self, bob, swap, callback_contract, pool_tokens, send pool_sending_balance_after = pool_tokens[sending].balanceOf(swap.address) pool_receiving_balance_after = pool_tokens[receiving].balanceOf(swap.address) - # TODO: incorporate cases for different asset types: - assert bob_sending_balance_before - bob_sending_balance_after == amount_in - assert bob_receiving_balance_after - bob_receiving_balance_before == amount_out + return { + "amount_in": amount_in, + "amount_out": amount_out, + "bob": { + "sending_token": [bob_sending_balance_before, bob_sending_balance_after], + "receiving_token": [bob_receiving_balance_before, bob_receiving_balance_after], + }, + "swap": { + "sending_token": [pool_sending_balance_before, pool_sending_balance_after], + "receiving_token": [pool_receiving_balance_before, pool_receiving_balance_after], + }, + } + + return _transfer_and_swap + + @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") + class TestExchangeReceived: + + # TODO: need to permutate/combinate N_COIN combos. + @pytest.mark.only_for_token_types(0, 1, 2) + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_exchange_received_nonrebasing(self, bob, swap, transfer_and_swap, pool_tokens, sending, receiving): + + underlying = False + swap_data = transfer_and_swap(swap, pool_tokens, sending, receiving, underlying) - assert pool_sending_balance_after - pool_sending_balance_before == amount_in - assert pool_receiving_balance_before - pool_receiving_balance_after == amount_out + assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] + assert ( + swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] + == swap_data["amount_out"] + ) - # TODO: Add tests for exchange_underlying_received - # @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") - # class TestExchangeUnderlyingReceived: - # pass + assert ( + swap_data["swap"]["sending_token"][1] - swap_data["swap"]["sending_token"][0] == swap_data["amount_in"] + ) + assert ( + swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] + == swap_data["amount_out"] + ) From 44b14720a1b737624a6222288f361d175f2524b3 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:09:28 +0200 Subject: [PATCH 108/337] fix skip token types --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index a340c6f6..00cfbb19 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -168,7 +168,7 @@ def decimals(initial_decimals, pool_token_types): def skip_by_token_type(request, pool_token_types): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: - if not any(pool_token_type in only_for_token_types.args for pool_token_type in pool_token_types): + if not all(pool_token_type in only_for_token_types.args for pool_token_type in pool_token_types): pytest.skip("skipped because no tokens for these types") From 9145b61c2cda55afb69b9c82a8a179fc23f91035 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:21:13 +0200 Subject: [PATCH 109/337] fix fitures --- .github/workflows/test_factory.yaml | 8 +-- .github/workflows/test_token.yaml | 9 +-- contracts/main/CurveStableSwapMetaNG.vy | 15 +++-- tests/conftest.py | 71 ++++++++++++--------- tests/fixtures/constants.py | 8 ++- tests/fixtures/pools.py | 85 +++++++++++++++++-------- tests/fixtures/tokens.py | 34 +++++----- tests/gauge/test_rewards.py | 2 +- 8 files changed, 134 insertions(+), 98 deletions(-) diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index 64caf3bd..651d38eb 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -41,10 +41,4 @@ jobs: - name: Run Tests Basic run: | source .venv/bin/activate - pytest tests/test_factory.py --pool-size=2 --pool-type=basic --decimals=18,18 -n auto - -# TODO: uncomment after meta fix -# - name: Run Tests Meta -# run: | -# source .venv/bin/activate -# pytest tests/test_factory.py --pool-size=2 --pool-type=meta --decimals=18,18 -n auto + pytest tests/test_factory.py -n auto diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index a120b2af..6abef501 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -29,12 +29,7 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-interaction - - name: Run Test Basic + - name: Run Tests run: | source .venv/bin/activate - pytest tests/test_token.py --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto - - - name: Run Test Meta - run: | - source .venv/bin/activate - pytest tests/test_token.py --pool-size=2 --pool-type=meta --token-types=plain --decimals=18 -n auto + pytest tests/test_token.py --token-types=plain -n auto diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index d72827b9..e90f389a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -315,7 +315,10 @@ def __init__( for i in range(N_COINS_128): self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - self.admin_balances.append(0) # <--- this initialises storage for admin balances + + # --------------------------- initialize storage --------------------------- + self.stored_balances.append(0) + self.admin_balances.append(0) # --------------------------- ERC20 stuff ---------------------------- @@ -1266,7 +1269,7 @@ def _xp_mem( result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(N_COINS_128): # _rates[i] * _balances[i] / PRECISION - result[i] = unsafe_div(_rates[i] * _balances[i], PRECISION) + result.append(unsafe_div(_rates[i] * _balances[i], PRECISION)) return result @@ -1673,10 +1676,10 @@ def A() -> uint256: return self._A() / A_PRECISION -@view -@external -def A_precise() -> uint256: - return self._A() +# @view +# @external +# def A_precise() -> uint256: +# return self._A() @view diff --git a/tests/conftest.py b/tests/conftest.py index 00cfbb19..c88da8a7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ +import itertools import os -from itertools import combinations import boa import pytest @@ -25,9 +25,9 @@ def pytest_addoption(parser): help="pool size to test against", ) parser.addoption( - "--pool-type", + "--pool-types", action="store", - default="basic", + default="basic,meta", help="pool type to test against", ) parser.addoption( @@ -64,14 +64,13 @@ def pytest_generate_tests(metafunc): ids=[f"(PoolSize={pool_size})"], ) - pool_type = metafunc.config.getoption("pool_type") - if "pool_type" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("pool_types").split(",") metafunc.parametrize( "pool_type", - [pool_types[pool_type]], + [pool_types[pool_type] for pool_type in cli_options], indirect=True, - ids=[f"(PoolType={pool_type})"], + ids=[f"(PoolType={pool_type})" for pool_type in cli_options], ) if "pool_token_types" in metafunc.fixturenames: @@ -80,29 +79,30 @@ def pytest_generate_tests(metafunc): cli_options.remove("eth") cli_options = ["eth"] + cli_options - if pool_types[pool_type] == 0: - combs = list(combinations(cli_options, pool_size)) - if pool_size == 2: - # do not include (eth,eth) pair - for t in cli_options: - if t != "eth": - combs.append((t, t)) - - metafunc.parametrize( - "pool_token_types", - [(token_types[c[0]], token_types[c[1]]) for c in combs], - indirect=True, - ids=[f"(PoolTokenTypes={c})" for c in combs], - ) - else: - # workaround for generating tokens - # for meta pool only 1st coin is selected - metafunc.parametrize( - "pool_token_types", - [[token_types[c]] for c in cli_options], - indirect=True, - ids=[f"(PoolTokenTypes={c})" for c in cli_options], - ) + combinations = list(itertools.combinations(cli_options, pool_size)) + if pool_size == 2: + # do not include (eth,eth) pair + for t in cli_options: + if t != "eth": + combinations.append((t, t)) + + metafunc.parametrize( + "pool_token_types", + [[token_types[idx] for idx in c] for c in combinations], + indirect=True, + ids=[f"(PoolTokenTypes={c})" for c in combinations], + ) + + if "metapool_token_type" in metafunc.fixturenames: + cli_options = metafunc.config.getoption("token_types").split(",") + + # for meta pool only 1st coin is selected + metafunc.parametrize( + "metapool_token_type", + [token_types[c] for c in cli_options], + indirect=True, + ids=[f"(PoolTokenTypes={c})" for c in cli_options], + ) if "initial_decimals" in metafunc.fixturenames: cli_options = metafunc.config.getoption("decimals") @@ -140,6 +140,11 @@ def pool_token_types(request): return request.param +@pytest.fixture(scope="session") +def metapool_token_type(request): + return request.param + + @pytest.fixture(scope="session") def return_type(request): return request.param @@ -156,6 +161,12 @@ def decimals(initial_decimals, pool_token_types): return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] +@pytest.fixture(scope="session") +def meta_decimals(initial_decimals, metapool_token_type, decimals): + # eth and oracle tokens are always 18 decimals + return decimals[0] if metapool_token_type in [0, 3] else 18 + + # Usage # @pytest.mark.only_for_token_types(1,2) # diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 1ad936aa..7b100cf6 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -9,8 +9,12 @@ def initial_balance() -> int: @pytest.fixture(scope="module") -def initial_amounts(decimals: list[int]) -> list[int]: - return [INITIAL_AMOUNT * 10**precision for precision in decimals] +def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: + return ( + [INITIAL_AMOUNT * 10**precision for precision in decimals] + if pool_type == 0 + else [INITIAL_AMOUNT * 10**meta_decimals, INITIAL_AMOUNT * 10**18] + ) @pytest.fixture(scope="module") diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 3eede56d..81658872 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -3,10 +3,8 @@ from eth_utils import function_signature_to_4byte_selector -# Only initialize useful fixtures @pytest.fixture(scope="module") def swap( - request, deployer, factory, weth, @@ -15,12 +13,16 @@ def swap( pool_token_types, pool_tokens, zero_address, + amm_interface, + set_pool_implementations, + underlying_tokens, + base_pool, + amm_interface_meta, + add_base_pool, + set_metapool_implementations, ): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") if pool_type == 0: - amm_interface_plain = request.getfixturevalue("amm_interface") - _ = request.getfixturevalue("set_pool_implementations") - A = 2000 fee = 1000000 method_ids = [bytes(b"")] * pool_size @@ -51,15 +53,9 @@ def swap( pool = factory.deploy_plain_pool( "test", "test", [t.address for t in pool_tokens], A, fee, 866, 0, asset_types, method_ids, oracles ) - return amm_interface_plain.at(pool) + return amm_interface.at(pool) elif pool_type == 1: - base_pool = request.getfixturevalue("base_pool") - underlying_tokens = request.getfixturevalue("underlying_tokens") - amm_interface_meta = request.getfixturevalue("amm_interface_meta") - _ = request.getfixturevalue("add_base_pool") - _ = request.getfixturevalue("set_metapool_implementations") - A = 2000 fee = 1000000 method_id = bytes(b"") @@ -121,16 +117,6 @@ def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base 5000000000, ) base_pool_lp_token.set_minter(base_pool.address) - - amount = 1_000_000 - with boa.env.prank(alice): - for d, token in zip(base_pool_decimals, base_pool_tokens): - token._mint_for_testing(alice, amount * 10**d) - token.approve(base_pool.address, 2**256 - 1) - - base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) - base_pool_lp_token.transfer(zero_address, base_pool_lp_token.balanceOf(alice)) - return base_pool @@ -140,13 +126,56 @@ def is_eth_pool(pool_tokens, weth): return weth in pool_tokens +def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = 1_000_000 + with boa.env.prank(user): + for d, token in zip(base_pool_decimals, base_pool_tokens): + token._mint_for_testing(user, amount * 10**d) + token.approve(base_pool.address, 2**256 - 1) + base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) + + @pytest.fixture(scope="module") -def add_initial_liquidity(owner, approve_owner, mint_owner, deposit_amounts, swap): - with boa.env.prank(owner): - swap.add_liquidity(deposit_amounts, 0) +def add_initial_liquidity_owner( + owner, + approve_owner, + mint_owner, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, +): + if pool_type == 0: + with boa.env.prank(owner): + swap.add_liquidity(deposit_amounts, 0) + else: + add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(owner): + base_pool_lp_token.approve(swap.address, 2**256 - 1) + swap.add_liquidity(deposit_amounts, 0) @pytest.fixture(scope="module") -def add_initial_liquidity_alice(alice, approve_alice, mint_alice, deposit_amounts, swap): - with boa.env.prank(alice): - swap.add_liquidity(deposit_amounts, 0) +def add_initial_liquidity_alice( + alice, + approve_alice, + mint_alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, +): + if pool_type == 0: + with boa.env.prank(alice): + swap.add_liquidity(deposit_amounts, 0) + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(alice): + base_pool_lp_token.approve(swap.address, 2**256 - 1) + swap.add_liquidity(deposit_amounts, 0) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 5b661663..0874f908 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -70,9 +70,23 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke # <--------------------- Metapool configuration ---------------------> +@pytest.fixture(scope="module") +def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebase_tokens): + if metapool_token_type == 0: + return plain_tokens[0] + elif metapool_token_type == 1: + return weth + elif metapool_token_type == 2: + return oracle_tokens[0] + elif metapool_token_type == 3: + return rebase_tokens[0] + else: + raise ValueError("Wrong pool token type") + + @pytest.fixture(scope="module") def base_pool_decimals(): - return [18, 6, 6] + return [18, 18, 18] @pytest.fixture(scope="module") @@ -92,22 +106,8 @@ def base_pool_lp_token(deployer): @pytest.fixture(scope="module") -def underlying_tokens( - pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens, base_pool_tokens, base_pool_lp_token -): - metapool_token_type = pool_token_types[0] - if metapool_token_type == 0: - pool_token = plain_tokens[0] - elif metapool_token_type == 1: - pool_token = weth - elif metapool_token_type == 2: - pool_token = oracle_tokens[0] - elif metapool_token_type == 3: - pool_token = rebase_tokens[0] - else: - raise ValueError("Wrong pool token type") - - return [pool_token] + [base_pool_lp_token, *base_pool_tokens] +def underlying_tokens(metapool_token, base_pool_tokens, base_pool_lp_token): + return [metapool_token, base_pool_lp_token, *base_pool_tokens] # <--------------------- Gauge rewards ---------------------> diff --git a/tests/gauge/test_rewards.py b/tests/gauge/test_rewards.py index 0568e9ba..a9554cd7 100644 --- a/tests/gauge/test_rewards.py +++ b/tests/gauge/test_rewards.py @@ -10,7 +10,7 @@ class TestGaugeRewards: class TestAddRewards: @pytest.fixture(autouse=True) - def initial_setup(self, owner, gauge, swap, add_initial_liquidity, set_gauge_implementation): + def initial_setup(self, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation): with boa.env.prank(owner): swap.approve(gauge.address, LP_AMOUNT) gauge.deposit(LP_AMOUNT) From 4f335e02b46f1e75755f8c2d4ab419cad50930f5 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:24:17 +0200 Subject: [PATCH 110/337] add bob fixtures --- contracts/main/CurveStableSwapFactoryNG.vy | 8 ++- tests/fixtures/pools.py | 17 ++++++ tests/test_factory.py | 70 +++------------------- 3 files changed, 29 insertions(+), 66 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index e6c9d8ad..38b57703 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -195,12 +195,14 @@ def get_underlying_coins(_pool: address) -> DynArray[address, MAX_COINS]: coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) base_pool: address = self.pool_data[_pool].base_pool assert base_pool != empty(address) # dev: pool is not metapool - coins[0] = self.pool_data[_pool].coins[0] + + coins.append(self.pool_data[_pool].coins[0]) + base_pool_n_coins: uint256 = len(self.base_pool_data[base_pool].coins) for i in range(1, MAX_COINS): - coins[i] = self.base_pool_data[base_pool].coins[i - 1] - if coins[i] == empty(address): + if (i - 1) == base_pool_n_coins: break + coins.append(self.base_pool_data[base_pool].coins[i - 1]) return coins diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 81658872..7b44f9aa 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -179,3 +179,20 @@ def add_initial_liquidity_alice( with boa.env.prank(alice): base_pool_lp_token.approve(swap.address, 2**256 - 1) swap.add_liquidity(deposit_amounts, 0) + + +@pytest.fixture(scope="module") +def mint_meta_bob( + bob, + mint_bob, + base_pool, + base_pool_tokens, + base_pool_decimals, +): + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + + +@pytest.fixture(scope="module") +def approve_meta_bob(bob, underlying_tokens, swap): + for token in underlying_tokens[:2]: + token.approve(swap.address, 2**256 - 1) diff --git a/tests/test_factory.py b/tests/test_factory.py index e5cea06b..ae5186b1 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -95,10 +95,10 @@ def test_get_underlying_coins(self, factory, swap, underlying_tokens): def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals - def test_get_metapool_rates(self, factory, swap, base_pool): + def test_get_metapool_rates(self, factory, swap, base_pool, add_initial_liquidity_alice): assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] - def test_get_underlying_balances(self, factory, swap, base_pool): + def test_get_underlying_balances(self, factory, swap, base_pool, add_initial_liquidity_alice): assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) @@ -121,17 +121,14 @@ def test_get_coin_indices_reverts(self, factory, swap, base_pool_lp_token, under with boa.reverts(): factory.get_coin_indices(swap.address, base_pool_lp_token.address, underlying_tokens[idx]) - # TODO: return after meta is fixed - # def test_get_implementation_address(self, factory, swap, amm_implementation_meta): - # assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address + def test_get_implementation_address(self, factory, swap, amm_implementation_meta): + assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is True - @pytest.mark.usefixtures("forked_chain") - @pytest.mark.only_for_pool_type(0) class TestFactoryAddPools: - def test_add_base_pool(self, factory, owner, add_base_pool): + def test_add_base_pool(self, factory, owner, add_base_pool, forked_chain): susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" coins = [ @@ -182,58 +179,6 @@ def test_add_base_pool_only_admin( sender=bob, ) - # @pytest.mark.skip - # def test_deploy_metapool(MetaUSD, new_factory, new_factory_setup, base_pool, bob): - # coin = ERC20(decimals=7) - # - # tx = new_factory.deploy_metapool(base_pool, "Name", "SYM", coin, 12345, 50000000, 0, {"from": bob}) - # assert tx.return_value == tx.new_contracts[0] - # swap = MetaUSD.at(tx.return_value) - # - # assert swap.coins(0) == coin - # assert swap.A() == 12345 - # assert swap.fee() == 50000000 - # - # assert new_factory.pool_count() == 1 - # assert new_factory.pool_list(0) == swap - # assert new_factory.get_decimals(swap) == [7, 18, 0, 0] - # - # @pytest.mark.skip - # def test_add_existing_metapools(factory, new_factory, fee_receiver, implementation_usd, base_pool, alice): - # assert new_factory.pool_count() == 0 - # # add existing USD pools to new factory - # new_factory.add_base_pool( - # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # new_factory.add_existing_metapools( - # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B", "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c"] - # + [ZERO_ADDRESS] * 8 - # ) - # assert new_factory.pool_count() == 2 - # assert new_factory.pool_list(0) == "0x5a6A4D54456819380173272A5E8E9B9904BdF41B" - # assert new_factory.pool_list(1) == "0x43b4FdFD4Ff969587185cDB6f0BD875c5Fc83f8c" - # assert ( - # new_factory.get_implementation_address("0x5a6A4D54456819380173272A5E8E9B9904BdF41B") - # == "0x5F890841f657d90E081bAbdB532A05996Af79Fe6" - # ) - # - # @pytest.mark.skip - # def test_add_existing_metapools_unknown_pool(swap, new_factory): - # with brownie.reverts("dev: pool not in old factory"): - # new_factory.add_existing_metapools([swap] + [ZERO_ADDRESS] * 9) - # - # @pytest.mark.skip - # def test_add_existing_metapools_duplicate_pool(new_factory, base_pool, implementation_usd, fee_receiver, - # alice): - # new_factory.add_base_pool( - # base_pool, fee_receiver, 0, [implementation_usd] + [ZERO_ADDRESS] * 9, {"from": alice} - # ) - # new_factory.add_existing_metapools(["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9) - # with brownie.reverts("dev: pool already exists"): - # new_factory.add_existing_metapools( - # ["0x5a6A4D54456819380173272A5E8E9B9904BdF41B"] + [ZERO_ADDRESS] * 9 - # ) - def test_deploy_plain_pool( self, factory, amm_interface, set_pool_implementations, pool_tokens, pool_size, zero_address ): @@ -262,7 +207,6 @@ def test_deploy_plain_pool( assert factory.pool_list(0) == swap.address assert factory.get_decimals(swap) == [t.decimals() for t in pool_tokens] - @pytest.mark.skip def test_pool_count( self, factory, @@ -274,7 +218,7 @@ def test_pool_count( pool_size, zero_address, ): - assert factory.pool_count() == 2 + assert factory.pool_count() == 3 _ = factory.deploy_plain_pool( "test", @@ -288,4 +232,4 @@ def test_pool_count( [bytes(b"")] * pool_size, [zero_address] * pool_size, ) - assert factory.pool_count() == 3 + assert factory.pool_count() == 4 From 3e96cf112ad8b2b9bb98b4160a2aaa883f142af8 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 20 Jul 2023 00:39:23 +0200 Subject: [PATCH 111/337] clear state after each test --- tests/conftest.py | 18 +++++++-------- tests/fixtures/accounts.py | 34 ++++++++++++++--------------- tests/fixtures/constants.py | 8 +++---- tests/fixtures/factory.py | 32 +++++++++++++-------------- tests/fixtures/pools.py | 14 ++++++------ tests/fixtures/tokens.py | 28 ++++++++++++------------ tests/pools/basic/test_liquidity.py | 2 +- tests/pools/test_optimistic_swap.py | 4 ++-- tests/test_factory.py | 5 +++-- 9 files changed, 73 insertions(+), 72 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c88da8a7..d9c2cdfc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,43 +125,43 @@ def pytest_generate_tests(metafunc): ) -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def pool_size(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def pool_type(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def pool_token_types(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def metapool_token_type(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def return_type(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def initial_decimals(request): return request.param -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def decimals(initial_decimals, pool_token_types): # eth and oracle tokens are always 18 decimals return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def meta_decimals(initial_decimals, metapool_token_type, decimals): # eth and oracle tokens are always 18 decimals return decimals[0] if metapool_token_type in [0, 3] else 18 @@ -194,7 +194,7 @@ def skip_by_pool_type(request, pool_type): pytest.skip("skipped because another pool type") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") assert rpc_url is not None, "Provider url is not set, add WEB3_PROVIDER_URL param to env" diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 10f8a650..ba5ad84e 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -6,57 +6,57 @@ from tests.utils.tokens import mint_for_testing -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def deployer() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def owner() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def fee_receiver() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def eth_acc() -> LocalAccount: return Account.create() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def alice(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def bob(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def charlie(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def dave(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def erin(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def frank(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def accounts(bob, charlie, dave, erin, frank): return [bob, charlie, dave, erin, frank] @@ -74,31 +74,31 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): mint_account(owner, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_owner(owner, pool_tokens, swap): approve_account(owner, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): mint_account(bob, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 7b100cf6..d264b142 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -3,12 +3,12 @@ INITIAL_AMOUNT = 1_000_000 -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def initial_balance() -> int: return INITIAL_AMOUNT * 10**18 -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: return ( [INITIAL_AMOUNT * 10**precision for precision in decimals] @@ -17,11 +17,11 @@ def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: ) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def deposit_amounts(initial_amounts: list[int]) -> list[int]: return [ia // 2 for ia in initial_amounts] -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def zero_address() -> str: return "0x0000000000000000000000000000000000000000" diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index d25e27a0..90074821 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -2,52 +2,52 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def gauge_interface(): return boa.load_partial("contracts/main/LiquidityGauge.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def gauge_implementation(deployer, gauge_interface): with boa.env.prank(deployer): return gauge_interface.deploy_as_blueprint() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def amm_interface(): return boa.load_partial("contracts/main/CurveStableSwapNG.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def amm_implementation(deployer, amm_interface): with boa.env.prank(deployer): return amm_interface.deploy_as_blueprint() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def amm_interface_meta(): return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def amm_implementation_meta(deployer, amm_interface_meta): with boa.env.prank(deployer): return amm_interface_meta.deploy_as_blueprint() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def views_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGViews.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def math_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGMath.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def factory( deployer, fee_receiver, @@ -74,19 +74,19 @@ def factory( # <--------------------- Functions ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def set_pool_implementations(owner, factory, amm_implementation): with boa.env.prank(owner): factory.set_pool_implementations(0, amm_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def set_metapool_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): factory.set_metapool_implementations(0, amm_implementation_meta.address) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def add_base_pool( owner, factory, @@ -104,25 +104,25 @@ def add_base_pool( ) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def set_gauge_implementation(owner, factory, gauge_implementation): with boa.env.prank(owner): factory.set_gauge_implementation(gauge_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def set_views_implementation(owner, factory, views_implementation): with boa.env.prank(owner): factory.set_views_implementation(views_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def set_math_implementation(owner, factory, math_implementation): with boa.env.prank(owner): factory.set_math_implementation(math_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): with boa.env.prank(owner): gauge_address = factory.deploy_gauge(swap.address) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 7b44f9aa..698dff2b 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -3,7 +3,7 @@ from eth_utils import function_signature_to_4byte_selector -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def swap( deployer, factory, @@ -104,7 +104,7 @@ def swap( # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): with boa.env.prank(deployer): base_pool = boa.load( @@ -121,7 +121,7 @@ def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base # <--------------------- Functions ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def is_eth_pool(pool_tokens, weth): return weth in pool_tokens @@ -135,7 +135,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def add_initial_liquidity_owner( owner, approve_owner, @@ -158,7 +158,7 @@ def add_initial_liquidity_owner( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def add_initial_liquidity_alice( alice, approve_alice, @@ -181,7 +181,7 @@ def add_initial_liquidity_alice( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_meta_bob( bob, mint_bob, @@ -192,7 +192,7 @@ def mint_meta_bob( add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_meta_bob(bob, underlying_tokens, swap): for token in underlying_tokens[:2]: token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 0874f908..f53443de 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def plain_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -11,13 +11,13 @@ def plain_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def weth(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/WETH.vy") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def oracle_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -42,7 +42,7 @@ def oracle_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -51,7 +51,7 @@ def rebase_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens): pool_tokens = [] for i, t in enumerate(pool_token_types): @@ -70,7 +70,7 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebase_tokens): if metapool_token_type == 0: return plain_tokens[0] @@ -84,12 +84,12 @@ def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebas raise ValueError("Wrong pool token type") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def base_pool_decimals(): return [18, 18, 18] -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def base_pool_tokens(deployer, base_pool_decimals): tokens = [] with boa.env.prank(deployer): @@ -99,37 +99,37 @@ def base_pool_tokens(deployer, base_pool_decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def base_pool_lp_token(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/CurveTokenV3.vy", "LP", "LP") -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def underlying_tokens(metapool_token, base_pool_tokens, base_pool_lp_token): return [metapool_token, base_pool_lp_token, *base_pool_tokens] # <--------------------- Gauge rewards ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def coin_reward(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CR", "CR", 18) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def coin_reward_a(owner, mint_owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRa", "CRa", 18) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def coin_reward_b(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRb", "CRb", 18) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def coin_rewards_additional(owner): coins = [] with boa.env.prank(owner): diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index b29c485d..23c76215 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -9,7 +9,7 @@ class TestLiquidityMethods: @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") class TestAddLiquidity: - @pytest.mark.parametrize("use_eth", (True, False), scope="session") + @pytest.mark.parametrize("use_eth", (True, False), scope="function") def test_add_liquidity( self, bob, diff --git a/tests/pools/test_optimistic_swap.py b/tests/pools/test_optimistic_swap.py index 09226e1d..96445479 100644 --- a/tests/pools/test_optimistic_swap.py +++ b/tests/pools/test_optimistic_swap.py @@ -7,7 +7,7 @@ class TestOptimisticSwap: - @pytest.fixture(scope="module") + @pytest.fixture(scope="function") def callback_contract(self, bob, swap, mint_bob, pool_tokens): with boa.env.prank(bob): @@ -17,7 +17,7 @@ def callback_contract(self, bob, swap, mint_bob, pool_tokens): return _callback - @pytest.fixture(scope="module") + @pytest.fixture(scope="function") def transfer_and_swap(self, callback_contract, bob): def _transfer_and_swap(swap, pool_tokens, sending, receiving, underlying): diff --git a/tests/test_factory.py b/tests/test_factory.py index ae5186b1..772ca703 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -138,6 +138,7 @@ def test_add_base_pool(self, factory, owner, add_base_pool, forked_chain): "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", ] + assert factory.base_pool_count() == 1 factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) assert factory.base_pool_count() == 2 assert factory.base_pool_list(1) == susd_pool @@ -218,7 +219,7 @@ def test_pool_count( pool_size, zero_address, ): - assert factory.pool_count() == 3 + assert factory.pool_count() == 1 _ = factory.deploy_plain_pool( "test", @@ -232,4 +233,4 @@ def test_pool_count( [bytes(b"")] * pool_size, [zero_address] * pool_size, ) - assert factory.pool_count() == 4 + assert factory.pool_count() == 2 From 108a87ffcf99c3fad73d33b6db7b680c086289a5 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:57:32 +0200 Subject: [PATCH 112/337] change scope modules --- tests/conftest.py | 18 +++++++-------- tests/fixtures/accounts.py | 34 ++++++++++++++--------------- tests/fixtures/constants.py | 8 +++---- tests/fixtures/factory.py | 32 +++++++++++++-------------- tests/fixtures/pools.py | 14 ++++++------ tests/fixtures/tokens.py | 28 ++++++++++++------------ tests/pools/basic/test_liquidity.py | 2 +- tests/pools/test_optimistic_swap.py | 4 ++-- 8 files changed, 70 insertions(+), 70 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d9c2cdfc..8b688352 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,43 +125,43 @@ def pytest_generate_tests(metafunc): ) -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def pool_size(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def pool_type(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def pool_token_types(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def metapool_token_type(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def return_type(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def initial_decimals(request): return request.param -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def decimals(initial_decimals, pool_token_types): # eth and oracle tokens are always 18 decimals return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def meta_decimals(initial_decimals, metapool_token_type, decimals): # eth and oracle tokens are always 18 decimals return decimals[0] if metapool_token_type in [0, 3] else 18 @@ -194,7 +194,7 @@ def skip_by_pool_type(request, pool_type): pytest.skip("skipped because another pool type") -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") assert rpc_url is not None, "Provider url is not set, add WEB3_PROVIDER_URL param to env" diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index ba5ad84e..10f8a650 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -6,57 +6,57 @@ from tests.utils.tokens import mint_for_testing -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def deployer() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def owner() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def fee_receiver() -> AddressType: return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def eth_acc() -> LocalAccount: return Account.create() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def alice(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def bob(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def charlie(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def dave(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def erin(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def frank(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def accounts(bob, charlie, dave, erin, frank): return [bob, charlie, dave, erin, frank] @@ -74,31 +74,31 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): mint_account(owner, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_owner(owner, pool_tokens, swap): approve_account(owner, pool_tokens, swap) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): mint_account(bob, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index d264b142..7b100cf6 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -3,12 +3,12 @@ INITIAL_AMOUNT = 1_000_000 -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def initial_balance() -> int: return INITIAL_AMOUNT * 10**18 -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: return ( [INITIAL_AMOUNT * 10**precision for precision in decimals] @@ -17,11 +17,11 @@ def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: ) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def deposit_amounts(initial_amounts: list[int]) -> list[int]: return [ia // 2 for ia in initial_amounts] -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def zero_address() -> str: return "0x0000000000000000000000000000000000000000" diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 90074821..d25e27a0 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -2,52 +2,52 @@ import pytest -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def gauge_interface(): return boa.load_partial("contracts/main/LiquidityGauge.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def gauge_implementation(deployer, gauge_interface): with boa.env.prank(deployer): return gauge_interface.deploy_as_blueprint() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def amm_interface(): return boa.load_partial("contracts/main/CurveStableSwapNG.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def amm_implementation(deployer, amm_interface): with boa.env.prank(deployer): return amm_interface.deploy_as_blueprint() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def amm_interface_meta(): return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def amm_implementation_meta(deployer, amm_interface_meta): with boa.env.prank(deployer): return amm_interface_meta.deploy_as_blueprint() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def views_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGViews.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def math_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGMath.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def factory( deployer, fee_receiver, @@ -74,19 +74,19 @@ def factory( # <--------------------- Functions ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def set_pool_implementations(owner, factory, amm_implementation): with boa.env.prank(owner): factory.set_pool_implementations(0, amm_implementation.address) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def set_metapool_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): factory.set_metapool_implementations(0, amm_implementation_meta.address) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def add_base_pool( owner, factory, @@ -104,25 +104,25 @@ def add_base_pool( ) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def set_gauge_implementation(owner, factory, gauge_implementation): with boa.env.prank(owner): factory.set_gauge_implementation(gauge_implementation.address) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def set_views_implementation(owner, factory, views_implementation): with boa.env.prank(owner): factory.set_views_implementation(views_implementation.address) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def set_math_implementation(owner, factory, math_implementation): with boa.env.prank(owner): factory.set_math_implementation(math_implementation.address) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): with boa.env.prank(owner): gauge_address = factory.deploy_gauge(swap.address) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 698dff2b..7b44f9aa 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -3,7 +3,7 @@ from eth_utils import function_signature_to_4byte_selector -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def swap( deployer, factory, @@ -104,7 +104,7 @@ def swap( # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): with boa.env.prank(deployer): base_pool = boa.load( @@ -121,7 +121,7 @@ def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base # <--------------------- Functions ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def is_eth_pool(pool_tokens, weth): return weth in pool_tokens @@ -135,7 +135,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def add_initial_liquidity_owner( owner, approve_owner, @@ -158,7 +158,7 @@ def add_initial_liquidity_owner( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def add_initial_liquidity_alice( alice, approve_alice, @@ -181,7 +181,7 @@ def add_initial_liquidity_alice( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_meta_bob( bob, mint_bob, @@ -192,7 +192,7 @@ def mint_meta_bob( add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_meta_bob(bob, underlying_tokens, swap): for token in underlying_tokens[:2]: token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index f53443de..0874f908 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def plain_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -11,13 +11,13 @@ def plain_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def weth(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/WETH.vy") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def oracle_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -42,7 +42,7 @@ def oracle_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -51,7 +51,7 @@ def rebase_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens): pool_tokens = [] for i, t in enumerate(pool_token_types): @@ -70,7 +70,7 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebase_tokens): if metapool_token_type == 0: return plain_tokens[0] @@ -84,12 +84,12 @@ def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebas raise ValueError("Wrong pool token type") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def base_pool_decimals(): return [18, 18, 18] -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def base_pool_tokens(deployer, base_pool_decimals): tokens = [] with boa.env.prank(deployer): @@ -99,37 +99,37 @@ def base_pool_tokens(deployer, base_pool_decimals): return tokens -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def base_pool_lp_token(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/CurveTokenV3.vy", "LP", "LP") -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def underlying_tokens(metapool_token, base_pool_tokens, base_pool_lp_token): return [metapool_token, base_pool_lp_token, *base_pool_tokens] # <--------------------- Gauge rewards ---------------------> -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def coin_reward(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CR", "CR", 18) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def coin_reward_a(owner, mint_owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRa", "CRa", 18) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def coin_reward_b(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRb", "CRb", 18) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def coin_rewards_additional(owner): coins = [] with boa.env.prank(owner): diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 23c76215..f2e426fd 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -9,7 +9,7 @@ class TestLiquidityMethods: @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") class TestAddLiquidity: - @pytest.mark.parametrize("use_eth", (True, False), scope="function") + @pytest.mark.parametrize("use_eth", (True, False), scope="module") def test_add_liquidity( self, bob, diff --git a/tests/pools/test_optimistic_swap.py b/tests/pools/test_optimistic_swap.py index 96445479..09226e1d 100644 --- a/tests/pools/test_optimistic_swap.py +++ b/tests/pools/test_optimistic_swap.py @@ -7,7 +7,7 @@ class TestOptimisticSwap: - @pytest.fixture(scope="function") + @pytest.fixture(scope="module") def callback_contract(self, bob, swap, mint_bob, pool_tokens): with boa.env.prank(bob): @@ -17,7 +17,7 @@ def callback_contract(self, bob, swap, mint_bob, pool_tokens): return _callback - @pytest.fixture(scope="function") + @pytest.fixture(scope="module") def transfer_and_swap(self, callback_contract, bob): def _transfer_and_swap(swap, pool_tokens, sending, receiving, underlying): From ad352b178431d704e48d6ce8ebc8b2237448ddb4 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:01:23 +0200 Subject: [PATCH 113/337] merge fixes --- contracts/main/CurveStableSwapFactoryNG.vy | 15 +- contracts/main/CurveStableSwapMetaNG.vy | 87 ++++----- contracts/main/CurveStableSwapNG.vy | 28 ++- contracts/main/CurveStableSwapNGViews.vy | 15 +- tests/conftest.py | 1 + tests/fixtures/accounts.py | 10 +- tests/fixtures/mocks.py | 13 ++ tests/fixtures/pools.py | 10 +- tests/pools/test_exchange_received.py | 197 +++++++++++++++++++++ tests/pools/test_optimistic_swap.py | 81 --------- 10 files changed, 293 insertions(+), 164 deletions(-) create mode 100644 tests/fixtures/mocks.py create mode 100644 tests/pools/test_exchange_received.py delete mode 100644 tests/pools/test_optimistic_swap.py diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 38b57703..c7dd3c33 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -195,14 +195,12 @@ def get_underlying_coins(_pool: address) -> DynArray[address, MAX_COINS]: coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) base_pool: address = self.pool_data[_pool].base_pool assert base_pool != empty(address) # dev: pool is not metapool - - coins.append(self.pool_data[_pool].coins[0]) - base_pool_n_coins: uint256 = len(self.base_pool_data[base_pool].coins) + coins[0] = self.pool_data[_pool].coins[0] for i in range(1, MAX_COINS): - if (i - 1) == base_pool_n_coins: + coins[i] = self.base_pool_data[base_pool].coins[i - 1] + if coins[i] == empty(address): break - coins.append(self.base_pool_data[base_pool].coins[i - 1]) return coins @@ -214,11 +212,6 @@ def get_decimals(_pool: address) -> DynArray[uint256, MAX_COINS]: @param _pool Pool address @return uint256 list of decimals """ - if self.pool_data[_pool].base_pool != empty(address): - decimals: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - decimals = self.pool_data[_pool].decimals - decimals[1] = 18 - return decimals return self.pool_data[_pool].decimals @@ -674,7 +667,7 @@ def deploy_metapool( base_lp_token: address = self.base_pool_data[_base_pool].lp_token - self.pool_data[pool].decimals = [decimals, 0, 0, 0, 0, 0, 0, 0] + self.pool_data[pool].decimals = [decimals, 18, 0, 0, 0, 0, 0, 0] self.pool_data[pool].n_coins = 2 self.pool_data[pool].base_pool = _base_pool self.pool_data[pool].coins = [_coin, self.base_pool_data[_base_pool].lp_token] diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e90f389a..7383cc8f 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -55,6 +55,8 @@ interface ERC1271: interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dx_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def calc_token_amount( _amounts: DynArray[uint256, MAX_COINS], _is_deposit: bool, @@ -178,12 +180,12 @@ BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) -math: public(immutable(Math)) -factory: public(immutable(Factory)) +math: immutable(Math) +factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -asset_types: public(immutable(DynArray[uint8, MAX_COINS])) +asset_types: immutable(DynArray[uint8, MAX_COINS]) FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -200,7 +202,7 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -ADMIN_FEE: constant(uint256) = 5000000000 +admin_fee: constant(uint256) = 5000000000 MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -211,7 +213,7 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price +last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -315,10 +317,8 @@ def __init__( for i in range(N_COINS_128): self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - - # --------------------------- initialize storage --------------------------- + self.admin_balances.append(0) # <--- this initialises storage for admin balances self.stored_balances.append(0) - self.admin_balances.append(0) # --------------------------- ERC20 stuff ---------------------------- @@ -389,7 +389,7 @@ def _transfer_in( if expect_optimistic_transfer: - assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" + assert _incoming_coin_asset_type != 3 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -415,9 +415,9 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- if _incoming_coin_asset_type == 3: - assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! + assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! else: - assert dx == _dx, "Pool did not receive tokens for swap" + assert dx == _dx # dev: pool did not receive tokens for swap # ----------------------- Update Stored Balances ------------------------- @@ -801,8 +801,8 @@ def add_liquidity( # base_fee * difference / FEE_DENOMINATOR fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) - # fees[i] * ADMIN_FEE / FEE_DENOMINATOR - self.admin_balances[i] += unsafe_div(fees[i] * ADMIN_FEE, FEE_DENOMINATOR) + # fees[i] * admin_fee / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) @@ -850,8 +850,8 @@ def remove_liquidity_one_coin( dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" - # fee * ADMIN_FEE / FEE_DENOMINATOR - self.admin_balances[i] += unsafe_div(fee * ADMIN_FEE, FEE_DENOMINATOR) + # fee * admin_fee / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fee * admin_fee, FEE_DENOMINATOR) self._burnFrom(msg.sender, _burn_amount) @@ -910,8 +910,8 @@ def remove_liquidity_imbalance( # base_fee * difference / FEE_DENOMINATOR fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) - # fees[i] * ADMIN_FEE / FEE_DENOMINATOR - self.admin_balances[i] += unsafe_div(fees[i] * ADMIN_FEE, FEE_DENOMINATOR) + # fees[i] * admin_fee / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) new_balances[i] -= fees[i] @@ -1002,7 +1002,7 @@ def __exchange( dy = (dy - dy_fee) * PRECISION / rates[j] self.admin_balances[j] += ( - unsafe_div(dy_fee * ADMIN_FEE, FEE_DENOMINATOR) # dy_fee * ADMIN_FEE / FEE_DENOMINATOR + unsafe_div(dy_fee * admin_fee, FEE_DENOMINATOR) # dy_fee * admin_fee / FEE_DENOMINATOR ) * PRECISION / rates[j] # Calculate and store state prices: @@ -1129,11 +1129,12 @@ def _exchange_underlying( assert asset_types[i] != 3 # dev: rebasing coins not supported + # Since the coin belongs to the metapool, we need to check if we got more than + # what already exists in the pool (since last record): dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] - assert dx_w_fee == _dx self.stored_balances[meta_i] += dx_w_fee - dx_w_fee = ERC20(input_coin).balanceOf(self) - _dx + assert dx_w_fee == _dx, "Pool did not receive tokens for swap" else: @@ -1168,6 +1169,9 @@ def _exchange_underlying( dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) + # Adjust stored balances of meta-level tokens: + self.stored_balances[meta_j] -= dy + # Withdraw from the base pool if needed if j > 0: out_amount: uint256 = ERC20(output_coin).balanceOf(self) @@ -1176,9 +1180,6 @@ def _exchange_underlying( assert dy >= _min_dy - # Adjust stored balances: - self.stored_balances[meta_j] -= dy - else: # base pool swap (user should swap at base pool for better gas) dy = ERC20(output_coin).balanceOf(self) @@ -1362,7 +1363,9 @@ def _get_p( p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) # ANN * xp[0] / A_PRECISION xp0_A: uint256 = unsafe_div(ANN * xp[0], A_PRECISION) - p.append(10**18 * (xp0_A + Dr * xp[0] / xp[1]) / (xp0_A + Dr)) + p.append( + 10**18 * (xp0_A + unsafe_div(Dr * xp[0], xp[1])) / (xp0_A + Dr) + ) return p @@ -1401,7 +1404,7 @@ def _ma_price() -> uint256: if ma_last_time < block.timestamp: alpha: uint256 = math.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) - return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 + return unsafe_div(last_price * (10**18 - alpha) + last_ema_price * alpha, 10**18) else: return last_ema_price @@ -1572,7 +1575,7 @@ def permit( assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner self.allowance[_owner][_spender] = _value - self.nonces[_owner] = nonce + 1 + self.nonces[_owner] = unsafe_add(nonce, 1) log Approval(_owner, _spender, _value) return True @@ -1605,6 +1608,12 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) +@view +@external +def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: + return StableSwapViews(factory.views_implementation()).get_dx_underlying(i, j, dy, self) + + @view @external def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @@ -1619,6 +1628,12 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) +@view +@external +def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: + return StableSwapViews(factory.views_implementation()).get_dy_underlying(i, j, dx, self) + + @view @external def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: @@ -1640,9 +1655,8 @@ def get_virtual_price() -> uint256: @dev Useful for calculating profits @return LP token virtual price normalized to 1e18 """ - amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = math.get_D(xp, amp, N_COINS) + D: uint256 = math.get_D(xp, self._A(), N_COINS) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio return D * PRECISION / self.totalSupply @@ -1660,26 +1674,19 @@ def calc_token_amount( @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - views: address = factory.views_implementation() - return StableSwapViews(views).calc_token_amount(_amounts, _is_deposit, self) + return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self) @view @external -def admin_fee() -> uint256: - return ADMIN_FEE +def A() -> uint256: + return unsafe_div(self._A(), A_PRECISION) @view @external -def A() -> uint256: - return self._A() / A_PRECISION - - -# @view -# @external -# def A_precise() -> uint256: -# return self._A() +def A_precise() -> uint256: + return self._A() @view diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 0cdeceaf..005d1ae0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -145,11 +145,11 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -factory: public(immutable(Factory)) +factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -asset_types: public(DynArray[uint8, MAX_COINS]) +asset_types: DynArray[uint8, MAX_COINS] FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -166,7 +166,7 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -ADMIN_FEE: constant(uint256) = 5000000000 +admin_fee: constant(uint256) = 5000000000 MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -177,7 +177,7 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price +last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -371,7 +371,7 @@ def _transfer_in( elif expect_optimistic_transfer: - assert _incoming_coin_asset_type != 3, "exchange_received not allowed if incoming token is rebasing" + assert _incoming_coin_asset_type != 3 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -397,9 +397,9 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- if _incoming_coin_asset_type == 3: - assert _dx > 0, "Pool did not receive tokens for swap" # TODO: Check this!! + assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! else: - assert dx == _dx, "Pool did not receive tokens for swap" + assert dx == _dx # dev: pool did not receive tokens for swap # ----------------------- Update Stored Balances ------------------------- @@ -695,7 +695,7 @@ def add_liquidity( difference = new_balance - ideal_balance fees.append(base_fee * difference / FEE_DENOMINATOR) - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) @@ -743,7 +743,7 @@ def remove_liquidity_one_coin( dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" - self.admin_balances[i] += fee * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += fee * admin_fee / FEE_DENOMINATOR self._burnFrom(msg.sender, _burn_amount) @@ -804,7 +804,7 @@ def remove_liquidity_imbalance( difference = new_balance - ideal_balance fees[i] = base_fee * difference / FEE_DENOMINATOR - self.admin_balances[i] += fees[i] * ADMIN_FEE / FEE_DENOMINATOR + self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] D2: uint256 = self.get_D_mem(rates, new_balances, amp) @@ -899,7 +899,7 @@ def __exchange( dy = (dy - dy_fee) * PRECISION / rates[j] self.admin_balances[j] += ( - dy_fee * ADMIN_FEE / FEE_DENOMINATOR + dy_fee * admin_fee / FEE_DENOMINATOR ) * PRECISION / rates[j] # Calculate and store state prices: @@ -1696,12 +1696,6 @@ def calc_token_amount( return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) -@view -@external -def admin_fee() -> uint256: - return ADMIN_FEE - - @view @external def A() -> uint256: diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 03bb9ec4..4d35e2a2 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -54,8 +54,11 @@ def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + D: uint256 = self.get_D(xp, amp, N_COINS) + y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwapNG(pool).fee()) - x: uint256 = self.get_y(j, i, y, xp, 0, 0, N_COINS) + x: uint256 = self.get_y(j, i, y, xp, amp, D, N_COINS) return (x - xp[i]) * PRECISION / rates[i] @@ -77,9 +80,11 @@ def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + D: uint256 = self.get_D(xp, amp, N_COINS) x: uint256 = xp[i] + (dx * rates[i] / PRECISION) - y: uint256 = self.get_y(i, j, x, xp, 0, 0, N_COINS) + y: uint256 = self.get_y(i, j, x, xp, amp, D, N_COINS) dy: uint256 = xp[j] - y - 1 fee: uint256 = StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR return (dy - fee) * PRECISION / rates[j] @@ -504,13 +509,13 @@ def _get_rates_balances_xp(pool: address, N_COINS: uint256) -> ( if idx == N_COINS: break rate = StableSwapNG(pool).stored_rates(idx) - rates[idx] = StableSwapNG(pool).stored_rates(idx) - balances[idx] = StableSwapNG(pool).balances(idx) + rates.append(StableSwapNG(pool).stored_rates(idx)) + balances.append(StableSwapNG(pool).balances(idx)) xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for idx in range(MAX_COINS): if idx == N_COINS: break - xp[idx] = rates[idx] * balances[idx] / PRECISION + xp.append(rates[idx] * balances[idx] / PRECISION) return rates, balances, xp diff --git a/tests/conftest.py b/tests/conftest.py index 8b688352..1abf06e9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ "tests.fixtures.accounts", "tests.fixtures.constants", "tests.fixtures.factory", + "tests.fixtures.mocks", "tests.fixtures.pools", "tests.fixtures.tokens", ] diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 10f8a650..d5cceb54 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -74,17 +74,17 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): mint_account(owner, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_owner(owner, pool_tokens, swap): approve_account(owner, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) @@ -94,11 +94,11 @@ def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): mint_account(bob, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py new file mode 100644 index 00000000..7555d1cb --- /dev/null +++ b/tests/fixtures/mocks.py @@ -0,0 +1,13 @@ +import boa +import pytest + + +@pytest.fixture(scope="module") +def callback_contract(bob, swap, pool_tokens, underlying_tokens): + + with boa.env.prank(bob): + _callback = boa.load("contracts/mocks/CallbackSwap.vy", swap.address, bob) + for token in pool_tokens + underlying_tokens: + token.approve(_callback.address, 2**256 - 1) + + return _callback diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 7b44f9aa..bded547e 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -135,7 +135,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def add_initial_liquidity_owner( owner, approve_owner, @@ -158,7 +158,7 @@ def add_initial_liquidity_owner( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def add_initial_liquidity_alice( alice, approve_alice, @@ -181,7 +181,7 @@ def add_initial_liquidity_alice( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def mint_meta_bob( bob, mint_bob, @@ -192,7 +192,7 @@ def mint_meta_bob( add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_meta_bob(bob, underlying_tokens, swap): - for token in underlying_tokens[:2]: + for token in underlying_tokens: token.approve(swap.address, 2**256 - 1) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py new file mode 100644 index 00000000..d77c975c --- /dev/null +++ b/tests/pools/test_exchange_received.py @@ -0,0 +1,197 @@ +import itertools + +import boa +import pytest + +SWAP_AMOUNT = 50 + + +@pytest.fixture(scope="function") +def transfer_and_swap( + callback_contract, + bob, + pool_tokens, + underlying_tokens, + pool_type, + base_pool, +): + def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): + + sending_token = "swap" + receiving_token = "swap" + + if pool_type == 1: + + input_coin = underlying_tokens[sending] + output_coin = underlying_tokens[receiving] + + # if sending == 0: + # input_coin = underlying_tokens[0] + # else: + # base_i = sending - 1 + # input_coin = underlying_tokens[2 + base_i] + # sending_token = "base_pool" + # if receiving == 0: + # output_coin = underlying_tokens[0] + # else: + # base_j = receiving - 1 + # output_coin = underlying_tokens[2 + base_j] + # receiving_token = "base_pool" + + else: + + input_coin = pool_tokens[sending] + output_coin = pool_tokens[receiving] + + amount_in = SWAP_AMOUNT * 10 ** (input_coin.decimals()) + + bob_sending_balance_before = input_coin.balanceOf(bob) + assert bob_sending_balance_before >= amount_in + + bob_receiving_balance_before = output_coin.balanceOf(bob) + pool_sending_balance_before = input_coin.balanceOf(pool.address) + pool_receiving_balance_before = output_coin.balanceOf(pool.address) + base_pool_sending_balance_before = input_coin.balanceOf(base_pool.address) + base_pool_receiving_balance_before = output_coin.balanceOf(base_pool.address) + + with boa.env.prank(bob): + amount_out = callback_contract.transfer_and_swap(sending, receiving, amount_in, 0, underlying) + assert amount_out > 0 + + bob_sending_balance_after = input_coin.balanceOf(bob) + bob_receiving_balance_after = output_coin.balanceOf(bob) + pool_sending_balance_after = input_coin.balanceOf(pool.address) + pool_receiving_balance_after = output_coin.balanceOf(pool.address) + base_pool_sending_balance_after = input_coin.balanceOf(base_pool.address) + base_pool_receiving_balance_after = output_coin.balanceOf(base_pool.address) + + return { + "amount_in": amount_in, + "amount_out": amount_out, + "sending_token_pool": sending_token, + "receiving_token_pool": receiving_token, + "bob": { + "sending_token": [bob_sending_balance_before, bob_sending_balance_after], + "receiving_token": [bob_receiving_balance_before, bob_receiving_balance_after], + }, + "swap": { + "sending_token": [pool_sending_balance_before, pool_sending_balance_after], + "receiving_token": [pool_receiving_balance_before, pool_receiving_balance_after], + }, + "base_pool": { + "sending_token": [base_pool_sending_balance_before, base_pool_sending_balance_after], + "receiving_token": [base_pool_receiving_balance_before, base_pool_receiving_balance_after], + }, + } + + return _transfer_and_swap + + +# TODO: need to permutate/combinate N_COIN combos. +@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_received_nonrebasing( + bob, + swap, + pool_tokens, + mint_bob, + mint_meta_bob, + approve_bob, + approve_meta_bob, + sending, + receiving, + add_initial_liquidity_owner, + transfer_and_swap, +): + swap_data = transfer_and_swap(swap, sending, receiving, False) + + assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] + assert swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] == swap_data["amount_out"] + + assert swap_data["swap"]["sending_token"][1] - swap_data["swap"]["sending_token"][0] == swap_data["amount_in"] + assert swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] == swap_data["amount_out"] + + +@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_not_received( + bob, swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity +): + + add_initial_liquidity() + + with boa.env.prank(bob), boa.reverts("Pool did not receive tokens for swap"): + swap.exchange_received(sending, receiving, 1, 0, False, bob) + + +@pytest.mark.only_for_token_types(3) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_received_rebasing_reverts( + bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity +): + + add_initial_liquidity() + + with boa.reverts(compiler="external call failed"): + transfer_and_swap(swap, sending, receiving, False) + + +@pytest.mark.only_for_pool_type(1) # only for metapools +@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) +def test_exchange_underlying_received_nonrebasing( + bob, + swap, + transfer_and_swap, + underlying_tokens, + mint_bob, + approve_bob, + sending, + receiving, + add_initial_liquidity, +): + + add_initial_liquidity() + + swap_data = transfer_and_swap(swap, sending, receiving, True) + + assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] + assert swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] == swap_data["amount_out"] + + # sending token swap balances should go up for sending_token_pool + # (could be base pool could be metapool): + sending_token_pool = swap_data["sending_token_pool"] + receiving_token_pool = swap_data["receiving_token_pool"] + assert ( + swap_data[sending_token_pool]["sending_token"][1] - swap_data[sending_token_pool]["sending_token"][0] + == swap_data["amount_in"] + ) + + # receiving token swap balances should go down for receiving_token_pool + # (could be base pool could be metapool): + assert ( + swap_data[receiving_token_pool]["receiving_token"][0] - swap_data[receiving_token_pool]["receiving_token"][1] + == swap_data["amount_out"] + ) + + +@pytest.mark.only_for_pool_type(1) # only for metapools +@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) +def test_exchange_underlying_not_received(bob, swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity): + add_initial_liquidity() + with boa.env.prank(bob), boa.reverts(): + swap.exchange_underlying_received(sending, receiving, 1, 0, False, bob) + + +@pytest.mark.only_for_pool_type(1) # only for metapools +@pytest.mark.only_for_token_types(3) +@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) +def test_exchange_underlying_received_rebasing_reverts( + swap, transfer_and_swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity +): + add_initial_liquidity() # <---- factory fixture that only adds liquidity when called + + if sending == 0: + with boa.reverts(compiler="external call failed"): + transfer_and_swap(swap, sending, receiving, True) diff --git a/tests/pools/test_optimistic_swap.py b/tests/pools/test_optimistic_swap.py deleted file mode 100644 index 09226e1d..00000000 --- a/tests/pools/test_optimistic_swap.py +++ /dev/null @@ -1,81 +0,0 @@ -import boa -import pytest - -# from tests.utils.transactions import call_returning_result_and_logs - -SWAP_AMOUNT = 500_000 - - -class TestOptimisticSwap: - @pytest.fixture(scope="module") - def callback_contract(self, bob, swap, mint_bob, pool_tokens): - - with boa.env.prank(bob): - _callback = boa.load("contracts/mocks/CallbackSwap.vy", swap.address, bob) - for token in pool_tokens: - token.approve(_callback.address, 2**256 - 1) - - return _callback - - @pytest.fixture(scope="module") - def transfer_and_swap(self, callback_contract, bob): - def _transfer_and_swap(swap, pool_tokens, sending, receiving, underlying): - - coin = pool_tokens[sending] - amount_in = SWAP_AMOUNT * 10 ** (coin.decimals()) - - assert coin.address == swap.coins(sending) - - bob_sending_balance_before = pool_tokens[sending].balanceOf(bob) - bob_receiving_balance_before = pool_tokens[receiving].balanceOf(bob) - pool_sending_balance_before = pool_tokens[sending].balanceOf(swap.address) - pool_receiving_balance_before = pool_tokens[receiving].balanceOf(swap.address) - - with boa.env.prank(bob): - amount_out = callback_contract.transfer_and_swap(sending, receiving, amount_in, 0, underlying) - assert amount_out > 0 - - bob_sending_balance_after = pool_tokens[sending].balanceOf(bob) - bob_receiving_balance_after = pool_tokens[receiving].balanceOf(bob) - pool_sending_balance_after = pool_tokens[sending].balanceOf(swap.address) - pool_receiving_balance_after = pool_tokens[receiving].balanceOf(swap.address) - - return { - "amount_in": amount_in, - "amount_out": amount_out, - "bob": { - "sending_token": [bob_sending_balance_before, bob_sending_balance_after], - "receiving_token": [bob_receiving_balance_before, bob_receiving_balance_after], - }, - "swap": { - "sending_token": [pool_sending_balance_before, pool_sending_balance_after], - "receiving_token": [pool_receiving_balance_before, pool_receiving_balance_after], - }, - } - - return _transfer_and_swap - - @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") - class TestExchangeReceived: - - # TODO: need to permutate/combinate N_COIN combos. - @pytest.mark.only_for_token_types(0, 1, 2) - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_exchange_received_nonrebasing(self, bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - - underlying = False - swap_data = transfer_and_swap(swap, pool_tokens, sending, receiving, underlying) - - assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] - assert ( - swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] - == swap_data["amount_out"] - ) - - assert ( - swap_data["swap"]["sending_token"][1] - swap_data["swap"]["sending_token"][0] == swap_data["amount_in"] - ) - assert ( - swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] - == swap_data["amount_out"] - ) From f6cc46c3fd5fc81defcd9531c11b1ac7f5e46406 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:21:49 +0200 Subject: [PATCH 114/337] remove use_eth, remove the ability to deposit native eth, remove any wrapping or unwrapping of eth to weth, add reentrancy check to totalSupply --- contracts/main/CurveStableSwapMetaNG.vy | 71 +++++++----------- contracts/main/CurveStableSwapNG.vy | 97 ++++++++----------------- 2 files changed, 58 insertions(+), 110 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 7383cc8f..10b97a7a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -229,7 +229,7 @@ version: public(constant(String[8])) = "v7.0.0" balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) -totalSupply: public(uint256) +total_supply: uint256 nonces: public(HashMap[address, uint256]) # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 @@ -357,7 +357,6 @@ def _transfer_in( callback_sig: bytes32, sender: address, receiver: address, - use_eth: bool, expect_optimistic_transfer: bool, ) -> uint256: """ @@ -370,7 +369,6 @@ def _transfer_in( dy: uint256 The `dy` that the pool enforces is actually min_dy. Callback only occurs for `exchange_extended`. - Callback cannot happen for `_use_eth` = True. @dev If callback_sig is empty, `_transfer_in` does a transferFrom. @params _coin address of the coin to transfer in. @params dx amount of `_coin` to transfer into the pool. @@ -379,7 +377,6 @@ def _transfer_in( @params callback_sig signature of the callback function. @params sender address to transfer `_coin` from. @params receiver address to transfer `_coin` to. - @params use_eth True if the transfer is ETH, False otherwise. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) @@ -428,7 +425,7 @@ def _transfer_in( @internal def _transfer_out( - _coin_idx: int128, _amount: uint256, use_eth: bool, receiver: address + _coin_idx: int128, _amount: uint256, receiver: address ): """ @notice Transfer a single token from the pool to receiver. @@ -436,7 +433,6 @@ def _transfer_out( `remove_liquidity_one` and `_exchange` methods. @params _coin Address of the token to transfer out @params _amount Amount of token to transfer out - @params use_eth Whether to transfer ETH or not @params receiver Address to send the tokens to """ @@ -516,7 +512,6 @@ def exchange( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -536,7 +531,6 @@ def exchange( j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -551,7 +545,6 @@ def exchange_extended( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _sender: address, _receiver: address, _cb: bytes32 @@ -574,7 +567,6 @@ def exchange_extended( j, _dx, _min_dy, - _use_eth, _receiver, msg.sender, # <---------------------------- callbacker is msg.sender. _cb, @@ -589,7 +581,6 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, ) -> uint256: """ @@ -612,7 +603,6 @@ def exchange_received( j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -627,19 +617,14 @@ def exchange_underlying( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token @param i Index value for the underlying coin to send @param j Index value of the underlying coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) - For MetaNG: native token wrapping/unwrapping is disabled; this - parameter can be whatever. @param _receiver Address that receives `j` @return Actual amount of `j` received """ @@ -663,18 +648,15 @@ def exchange_underlying_extended( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, _cb: bytes32 ) -> uint256: """ @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token @param i Index value for the underlying coin to send @param j Index value of the underlying coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) @param _receiver Address that receives `j` @return Actual amount of `j` received """ @@ -699,17 +681,14 @@ def exchange_underlying_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, ) -> uint256: """ @notice Perform an exchange between two underlying coins - @dev Even if _use_eth is in the abi, the method does not accept native token @param i Index value for the underlying coin to send @param j Index value of the underlying coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive - @param _use_eth Use native token transfers (if pool has WETH20) @param _receiver Address that receives `j` @return Actual amount of `j` received """ @@ -731,7 +710,6 @@ def exchange_underlying_received( def add_liquidity( _amounts: DynArray[uint256, MAX_COINS], _min_mint_amount: uint256, - _use_eth: bool = False, _receiver: address = msg.sender ) -> uint256: """ @@ -748,7 +726,7 @@ def add_liquidity( # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply new_balances: DynArray[uint256, MAX_COINS] = old_balances # -------------------------- Do Transfers In ----------------------------- @@ -765,7 +743,6 @@ def add_liquidity( empty(bytes32), msg.sender, empty(address), - _use_eth, False, # expect_optimistic_transfer ) @@ -819,7 +796,7 @@ def add_liquidity( # Mint pool tokens total_supply += mint_amount self.balanceOf[_receiver] += mint_amount - self.totalSupply = total_supply + self.total_supply = total_supply log Transfer(empty(address), _receiver, mint_amount) log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) @@ -833,7 +810,6 @@ def remove_liquidity_one_coin( _burn_amount: uint256, i: int128, _min_received: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -857,9 +833,9 @@ def remove_liquidity_one_coin( log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(i, dy, _use_eth, _receiver) + self._transfer_out(i, dy, _receiver) - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.totalSupply) + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) self.save_p_from_price(p) @@ -871,7 +847,6 @@ def remove_liquidity_one_coin( def remove_liquidity_imbalance( _amounts: DynArray[uint256, MAX_COINS], _max_burn_amount: uint256, - _use_eth: bool = False, _receiver: address = msg.sender ) -> uint256: """ @@ -891,7 +866,7 @@ def remove_liquidity_imbalance( if _amounts[i] != 0: new_balances[i] -= _amounts[i] - self._transfer_out(i, _amounts[i], _use_eth, _receiver) + self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -919,7 +894,7 @@ def remove_liquidity_imbalance( self.save_p(new_balances, amp, D2) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -936,7 +911,6 @@ def remove_liquidity_imbalance( def remove_liquidity( _burn_amount: uint256, _min_amounts: DynArray[uint256, MAX_COINS], - _use_eth: bool = False, _receiver: address = msg.sender, _claim_admin_fees: bool = True, ) -> DynArray[uint256, MAX_COINS]: @@ -948,7 +922,7 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -957,7 +931,7 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(i, value, _use_eth, _receiver) + self._transfer_out(i, value, _receiver) self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds @@ -1022,7 +996,6 @@ def _exchange( j: int128, _dx: uint256, _min_dy: uint256, - use_eth: bool, receiver: address, callbacker: address, callback_sig: bytes32, @@ -1047,7 +1020,6 @@ def _exchange( callback_sig, sender, receiver, - use_eth, expect_optimistic_transfer ) @@ -1060,7 +1032,7 @@ def _exchange( # --------------------------- Do Transfer out ---------------------------- - self._transfer_out(j, dy, use_eth, receiver) + self._transfer_out(j, dy, receiver) # ------------------------------------------------------------------------ @@ -1146,7 +1118,6 @@ def _exchange_underlying( callback_sig, sender, receiver, - False, # use_eth = False False, # expect_optimistic_transfer = False ) @@ -1229,7 +1200,7 @@ def _withdraw_admin_fees(): if admin_balances[i] > 0: - self._transfer_out(i, admin_balances[i], False, fee_receiver) + self._transfer_out(i, admin_balances[i], fee_receiver) admin_balances[i] = 0 self.admin_balances = admin_balances @@ -1304,7 +1275,7 @@ def _calc_withdraw_one_coin( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) D0: uint256 = math.get_D(xp, amp, N_COINS) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = math.get_y_D(amp, i, xp, D1, N_COINS) @@ -1479,7 +1450,7 @@ def _transfer(_from: address, _to: address, _value: uint256): @internal def _burnFrom(_from: address, _burn_amount: uint256): - self.totalSupply -= _burn_amount + self.total_supply -= _burn_amount self.balanceOf[_from] -= _burn_amount log Transfer(_from, empty(address), _burn_amount) @@ -1646,6 +1617,18 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: return self._calc_withdraw_one_coin(_burn_amount, i)[0] +@view +@external +@nonreentrant('lock') +def totalSupply() -> uint256: + """ + @notice The total supply of pool LP tokens + @dev reentrancy guarded, just in case. + @returns self.total_supply, 18 decimals. + """ + return self.total_supply + + @view @external @nonreentrant('lock') @@ -1659,7 +1642,7 @@ def get_virtual_price() -> uint256: D: uint256 = math.get_D(xp, self._A(), N_COINS) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio - return D * PRECISION / self.totalSupply + return D * PRECISION / self.total_supply @view diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 005d1ae0..e6ac859b 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -193,7 +193,7 @@ version: public(constant(String[8])) = "v7.0.0" balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) -totalSupply: public(uint256) +total_supply: uint256 nonces: public(HashMap[address, uint256]) # keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 @@ -316,24 +316,15 @@ def __init__( # ------------------ Token transfers in and out of the AMM ------------------- -@payable -@external -def __default__(): - if msg.value > 0: - assert WETH20 in coins - - @internal def _transfer_in( coin_idx: int128, dx: uint256, dy: uint256, - mvalue: uint256, callbacker: address, callback_sig: bytes32, sender: address, receiver: address, - use_eth: bool, expect_optimistic_transfer: bool, ) -> uint256: """ @@ -346,7 +337,6 @@ def _transfer_in( dy: uint256 The `dy` that the pool enforces is actually min_dy. Callback only occurs for `exchange_extended`. - Callback cannot happen for `_use_eth` = True. @dev If callback_sig is empty, `_transfer_in` does a transferFrom. @params _coin address of the coin to transfer in. @params dx amount of `_coin` to transfer into the pool. @@ -356,7 +346,6 @@ def _transfer_in( @params callback_sig signature of the callback function. @params sender address to transfer `_coin` from. @params receiver address to transfer `_coin` to. - @params use_eth True if the transfer is ETH, False otherwise. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) @@ -364,12 +353,7 @@ def _transfer_in( # ------------------------- Handle Transfers ----------------------------- - if use_eth and coins[coin_idx] == WETH20: - - _dx = mvalue - WETH(WETH20).deposit(value=dx) - - elif expect_optimistic_transfer: + if expect_optimistic_transfer: assert _incoming_coin_asset_type != 3 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] @@ -409,31 +393,21 @@ def _transfer_in( @internal -def _transfer_out( - _coin_idx: int128, _amount: uint256, use_eth: bool, receiver: address -): +def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): """ @notice Transfer a single token from the pool to receiver. @dev This function is called by `remove_liquidity` and `remove_liquidity_one` and `_exchange` methods. @params _coin Address of the token to transfer out @params _amount Amount of token to transfer out - @params use_eth Whether to transfer ETH or not @params receiver Address to send the tokens to """ # ------------------------- Handle Transfers ----------------------------- - if use_eth and coins[_coin_idx] == WETH20: - - WETH(WETH20).withdraw(_amount) - raw_call(receiver, b"", value=_amount) - - else: - - assert ERC20(coins[_coin_idx]).transfer( - receiver, _amount, default_return_value=True - ) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) # ----------------------- Update Stored Balances ------------------------- @@ -499,7 +473,6 @@ def _balances() -> DynArray[uint256, MAX_COINS]: # -------------------------- AMM Main Functions ------------------------------ -@payable @external @nonreentrant('lock') def exchange( @@ -507,7 +480,6 @@ def exchange( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -523,12 +495,10 @@ def exchange( """ return self._exchange( msg.sender, - msg.value, i, j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -543,7 +513,6 @@ def exchange_extended( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _sender: address, _receiver: address, _cb: bytes32 @@ -562,12 +531,10 @@ def exchange_extended( assert _cb != empty(bytes32) # dev: No callback specified return self._exchange( _sender, - 0, # mvalue is zero here i, j, _dx, _min_dy, - _use_eth, _receiver, msg.sender, # <---------------------------- callbacker is msg.sender. _cb, @@ -582,7 +549,6 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _use_eth: bool, _receiver: address, ) -> uint256: """ @@ -601,12 +567,10 @@ def exchange_received( """ return self._exchange( msg.sender, - 0, i, j, _dx, _min_dy, - _use_eth, _receiver, empty(address), empty(bytes32), @@ -614,13 +578,11 @@ def exchange_received( ) -@payable @external @nonreentrant('lock') def add_liquidity( _amounts: DynArray[uint256, MAX_COINS], _min_mint_amount: uint256, - _use_eth: bool = False, _receiver: address = msg.sender ) -> uint256: """ @@ -637,7 +599,7 @@ def add_liquidity( # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply new_balances: DynArray[uint256, MAX_COINS] = old_balances # -------------------------- Do Transfers In ----------------------------- @@ -653,12 +615,10 @@ def add_liquidity( i, _amounts[i], 0, - msg.value, empty(address), empty(bytes32), msg.sender, empty(address), - _use_eth, False, # expect_optimistic_transfer ) @@ -712,7 +672,7 @@ def add_liquidity( # Mint pool tokens total_supply += mint_amount self.balanceOf[_receiver] += mint_amount - self.totalSupply = total_supply + self.total_supply = total_supply log Transfer(empty(address), _receiver, mint_amount) log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) @@ -726,7 +686,6 @@ def remove_liquidity_one_coin( _burn_amount: uint256, i: int128, _min_received: uint256, - _use_eth: bool = False, _receiver: address = msg.sender, ) -> uint256: """ @@ -747,9 +706,9 @@ def remove_liquidity_one_coin( self._burnFrom(msg.sender, _burn_amount) - self._transfer_out(i, dy, _use_eth, _receiver) + self._transfer_out(i, dy, _receiver) - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.totalSupply) + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) self.save_p_from_price(p) @@ -761,7 +720,6 @@ def remove_liquidity_one_coin( def remove_liquidity_imbalance( _amounts: DynArray[uint256, MAX_COINS], _max_burn_amount: uint256, - _use_eth: bool = False, _receiver: address = msg.sender ) -> uint256: """ @@ -784,7 +742,7 @@ def remove_liquidity_imbalance( if _amounts[i] != 0: new_balances[i] -= _amounts[i] - self._transfer_out(i, _amounts[i], _use_eth, _receiver) + self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -811,7 +769,7 @@ def remove_liquidity_imbalance( self.save_p(new_balances, amp, D2) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -829,7 +787,6 @@ def remove_liquidity_imbalance( def remove_liquidity( _burn_amount: uint256, _min_amounts: DynArray[uint256, MAX_COINS], - _use_eth: bool = False, _receiver: address = msg.sender, _claim_admin_fees: bool = True, ) -> DynArray[uint256, MAX_COINS]: @@ -841,7 +798,7 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -853,7 +810,7 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts[i] = value - self._transfer_out(i, value, _use_eth, _receiver) + self._transfer_out(i, value, _receiver) total_supply -= _burn_amount self._burnFrom(msg.sender, _burn_amount) @@ -915,12 +872,10 @@ def __exchange( @internal def _exchange( sender: address, - mvalue: uint256, i: int128, j: int128, _dx: uint256, _min_dy: uint256, - use_eth: bool, receiver: address, callbacker: address, callback_sig: bytes32, @@ -941,12 +896,10 @@ def _exchange( i, _dx, _min_dy, - mvalue, callbacker, callback_sig, sender, receiver, - use_eth, expect_optimistic_transfer ) @@ -958,7 +911,7 @@ def _exchange( # --------------------------- Do Transfer out ---------------------------- - self._transfer_out(j, dy, use_eth, receiver) + self._transfer_out(j, dy, receiver) # ------------------------------------------------------------------------ @@ -981,7 +934,7 @@ def _withdraw_admin_fees(): if admin_balances[i] > 0: - self._transfer_out(i, admin_balances[i], False, fee_receiver) + self._transfer_out(i, admin_balances[i], fee_receiver) admin_balances[i] = 0 self.admin_balances = admin_balances @@ -1239,7 +1192,7 @@ def _calc_withdraw_one_coin( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) D0: uint256 = self.get_D(xp, amp) - total_supply: uint256 = self.totalSupply + total_supply: uint256 = self.total_supply D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = self.get_y_D(amp, i, xp, D1) @@ -1499,7 +1452,7 @@ def _transfer(_from: address, _to: address, _value: uint256): @internal def _burnFrom(_from: address, _burn_amount: uint256): - self.totalSupply -= _burn_amount + self.total_supply -= _burn_amount self.balanceOf[_from] -= _burn_amount log Transfer(_from, empty(address), _burn_amount) @@ -1654,6 +1607,18 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: return self._calc_withdraw_one_coin(_burn_amount, i)[0] +@view +@external +@nonreentrant('lock') +def totalSupply() -> uint256: + """ + @notice The total supply of pool LP tokens + @dev reentrancy guarded, just in case. + @returns self.total_supply, 18 decimals. + """ + return self.total_supply + + @view @external @nonreentrant('lock') @@ -1668,7 +1633,7 @@ def get_virtual_price() -> uint256: D: uint256 = self.get_D(xp, amp) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio - return D * PRECISION / self.totalSupply + return D * PRECISION / self.total_supply @view From e2ccfb3d5799fdec1c7be93ec73d0f669981204b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:45:31 +0200 Subject: [PATCH 115/337] wip: fixing tests --- contracts/main/CurveStableSwapMetaNG.vy | 3 ++- contracts/mocks/CallbackSwap.vy | 6 ------ tests/pools/test_exchange_received.py | 27 ++++++++++--------------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 10b97a7a..32a5e000 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1663,7 +1663,8 @@ def calc_token_amount( @view @external def A() -> uint256: - return unsafe_div(self._A(), A_PRECISION) + amp: uint256 = self._A() + return unsafe_div(amp, A_PRECISION) @view diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index 9a21156d..feac83ab 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -18,7 +18,6 @@ interface Swap: j: int128, dx: uint256, min_dy: uint256, - use_eth: bool, sender: address, receiver: address, cb: bytes32 @@ -28,7 +27,6 @@ interface Swap: j: int128, dx: uint256, min_dy: uint256, - use_eth: bool, receiver: address, ) -> uint256: nonpayable def exchange_underlying_received( @@ -36,7 +34,6 @@ interface Swap: j: int128, dx: uint256, min_dy: uint256, - use_eth: bool, receiver: address, ) -> uint256: nonpayable @@ -120,7 +117,6 @@ def callback_and_swap( j, # output coin index dx, # amount in min_dy, # minimum expected out - False, # use native token (eth) msg.sender, # sender (doesnt matter because we set it to the vault in the callback) keeper, # receiver convert(selector, bytes32) # <-- your callback is being called here @@ -147,7 +143,6 @@ def transfer_and_swap( j, # output coin index dx, # amount in min_dy, # minimum expected out - False, # use native token (eth) keeper, # receiver ) @@ -156,6 +151,5 @@ def transfer_and_swap( j, # output coin index dx, # amount in min_dy, # minimum expected out - False, # use native token (eth) keeper, # receiver ) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index d77c975c..f3047b5f 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -115,23 +115,19 @@ def test_exchange_received_nonrebasing( @pytest.mark.only_for_token_types(0, 1, 2) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_not_received( - bob, swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity + bob, swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner ): - add_initial_liquidity() - - with boa.env.prank(bob), boa.reverts("Pool did not receive tokens for swap"): - swap.exchange_received(sending, receiving, 1, 0, False, bob) + with boa.env.prank(bob), boa.reverts(): + swap.exchange_received(sending, receiving, 1, 0, bob) @pytest.mark.only_for_token_types(3) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts( - bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity + bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner ): - add_initial_liquidity() - with boa.reverts(compiler="external call failed"): transfer_and_swap(swap, sending, receiving, False) @@ -148,11 +144,9 @@ def test_exchange_underlying_received_nonrebasing( approve_bob, sending, receiving, - add_initial_liquidity, + add_initial_liquidity_owner, ): - add_initial_liquidity() - swap_data = transfer_and_swap(swap, sending, receiving, True) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] @@ -178,19 +172,20 @@ def test_exchange_underlying_received_nonrebasing( @pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.only_for_token_types(0, 1, 2) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_not_received(bob, swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity): - add_initial_liquidity() +def test_exchange_underlying_not_received( + bob, swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner +): + with boa.env.prank(bob), boa.reverts(): - swap.exchange_underlying_received(sending, receiving, 1, 0, False, bob) + swap.exchange_underlying_received(sending, receiving, 1, 0, bob) @pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.only_for_token_types(3) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts( - swap, transfer_and_swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity + swap, transfer_and_swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner ): - add_initial_liquidity() # <---- factory fixture that only adds liquidity when called if sending == 0: with boa.reverts(compiler="external call failed"): From aa64249c204fbfb9f86747d924faf4035259c5ba Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 21 Jul 2023 18:08:35 +0200 Subject: [PATCH 116/337] test_exchange_received works --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/mocks/CallbackSwap.vy | 4 +- tests/fixtures/pools.py | 15 ++++++- tests/pools/test_exchange_received.py | 57 +++++++++++++++++-------- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 32a5e000..f7921cd7 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -776,7 +776,7 @@ def add_liquidity( difference = new_balance - ideal_balance # base_fee * difference / FEE_DENOMINATOR - fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) + fees.append(unsafe_div(base_fee * difference, FEE_DENOMINATOR)) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index feac83ab..81102d1c 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -127,6 +127,7 @@ def callback_and_swap( def transfer_and_swap( i: int128, j: int128, + input_coin: address, dx: uint256, min_dy: uint256, underlying: bool @@ -134,8 +135,7 @@ def transfer_and_swap( assert msg.sender == keeper - coin: address = whitelisted_pool.coins(convert(i, uint256)) - ERC20(coin).transferFrom(keeper, whitelisted_pool.address, dx) + ERC20(input_coin).transferFrom(keeper, whitelisted_pool.address, dx) if not underlying: return whitelisted_pool.exchange_received( diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index bded547e..40fcc017 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,6 +2,8 @@ import pytest from eth_utils import function_signature_to_4byte_selector +from tests.utils.tokens import mint_for_testing + @pytest.fixture(scope="module") def swap( @@ -132,7 +134,9 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal for d, token in zip(base_pool_decimals, base_pool_tokens): token._mint_for_testing(user, amount * 10**d) token.approve(base_pool.address, 2**256 - 1) - base_pool.add_liquidity([amount * 10**d for d in base_pool_decimals], 0) + + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) @pytest.fixture(scope="function") @@ -143,6 +147,7 @@ def add_initial_liquidity_owner( deposit_amounts, swap, pool_type, + underlying_tokens, base_pool, base_pool_tokens, base_pool_decimals, @@ -155,7 +160,13 @@ def add_initial_liquidity_owner( add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) with boa.env.prank(owner): base_pool_lp_token.approve(swap.address, 2**256 - 1) - swap.add_liquidity(deposit_amounts, 0) + lp_token_bal = base_pool_lp_token.balanceOf(owner) + to_mint_token0 = lp_token_bal * 10 ** underlying_tokens[0].decimals() // 10 ** base_pool_lp_token.decimals() + + mint_for_testing(owner, to_mint_token0, underlying_tokens[0], False) + underlying_tokens[0].approve(swap.address, 2**256 - 1) + + swap.add_liquidity([to_mint_token0, lp_token_bal], 0) @pytest.fixture(scope="function") diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index f3047b5f..9c017178 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -3,6 +3,9 @@ import boa import pytest +from tests.fixtures.pools import add_base_pool_liquidity +from tests.utils.tokens import mint_for_testing + SWAP_AMOUNT = 50 @@ -14,50 +17,68 @@ def transfer_and_swap( underlying_tokens, pool_type, base_pool, + mint_meta_bob, + base_pool_lp_token, + base_pool_tokens, + base_pool_decimals, ): def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): + # get input and output tokens: sending_token = "swap" receiving_token = "swap" if pool_type == 1: - input_coin = underlying_tokens[sending] - output_coin = underlying_tokens[receiving] - - # if sending == 0: - # input_coin = underlying_tokens[0] - # else: - # base_i = sending - 1 - # input_coin = underlying_tokens[2 + base_i] - # sending_token = "base_pool" - # if receiving == 0: - # output_coin = underlying_tokens[0] - # else: - # base_j = receiving - 1 - # output_coin = underlying_tokens[2 + base_j] - # receiving_token = "base_pool" + if underlying: + + if sending == 0: + input_coin = underlying_tokens[0] + else: + base_i = sending - 1 + input_coin = underlying_tokens[2 + base_i] + sending_token = "base_pool" + if receiving == 0: + output_coin = underlying_tokens[0] + else: + base_j = receiving - 1 + output_coin = underlying_tokens[2 + base_j] + receiving_token = "base_pool" + + else: + + input_coin = underlying_tokens[sending] + output_coin = underlying_tokens[receiving] else: input_coin = pool_tokens[sending] output_coin = pool_tokens[receiving] + # calc amount in: amount_in = SWAP_AMOUNT * 10 ** (input_coin.decimals()) - bob_sending_balance_before = input_coin.balanceOf(bob) - assert bob_sending_balance_before >= amount_in + # mint tokens if account does not have: + if input_coin.balanceOf(bob) < amount_in: + if input_coin == base_pool_lp_token: + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + else: + mint_for_testing(bob, amount_in, input_coin, False) + # record balances before + bob_sending_balance_before = input_coin.balanceOf(bob) bob_receiving_balance_before = output_coin.balanceOf(bob) pool_sending_balance_before = input_coin.balanceOf(pool.address) pool_receiving_balance_before = output_coin.balanceOf(pool.address) base_pool_sending_balance_before = input_coin.balanceOf(base_pool.address) base_pool_receiving_balance_before = output_coin.balanceOf(base_pool.address) + # swap with boa.env.prank(bob): - amount_out = callback_contract.transfer_and_swap(sending, receiving, amount_in, 0, underlying) + amount_out = callback_contract.transfer_and_swap(sending, receiving, input_coin, amount_in, 0, underlying) assert amount_out > 0 + # record balances after bob_sending_balance_after = input_coin.balanceOf(bob) bob_receiving_balance_after = output_coin.balanceOf(bob) pool_sending_balance_after = input_coin.balanceOf(pool.address) From 2ec1320e919034b2590c23dea46c6928cce84172 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 21 Jul 2023 23:53:03 +0200 Subject: [PATCH 117/337] remove weth --- .github/workflows/test_factory.yaml | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 26 +++-- contracts/main/CurveStableSwapMetaNG.vy | 25 +++-- contracts/main/CurveStableSwapNG.vy | 35 +++---- tests/conftest.py | 24 ++--- tests/fixtures/accounts.py | 88 ++++++++++++++++- tests/fixtures/factory.py | 2 - tests/fixtures/pools.py | 106 +-------------------- tests/fixtures/tokens.py | 18 +--- tests/pools/test_exchange_received.py | 14 +-- tests/test_factory.py | 59 +++++++++--- 11 files changed, 189 insertions(+), 210 deletions(-) diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index 651d38eb..d3b7a444 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -41,4 +41,4 @@ jobs: - name: Run Tests Basic run: | source .venv/bin/activate - pytest tests/test_factory.py -n auto + pytest tests/test_factory.py --token-types=plain -n auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index c7dd3c33..2f972873 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -44,7 +44,6 @@ interface CurvePool: j: int128, dx: uint256, min_dy: uint256, - _use_eth: bool, _receiver: address, ) -> uint256: nonpayable @@ -73,8 +72,6 @@ event LiquidityGaugeDeployed: pool: address gauge: address -WETH20: public(immutable(address)) - MAX_COINS: constant(uint256) = 8 ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 @@ -110,14 +107,11 @@ market_counts: HashMap[uint256, uint256] @external -def __init__(_fee_receiver: address, _owner: address, _weth: address): +def __init__(_fee_receiver: address, _owner: address): self.fee_receiver = _fee_receiver self.admin = _owner - WETH20 = _weth - - # <--- Factory Getters ---> @@ -195,12 +189,15 @@ def get_underlying_coins(_pool: address) -> DynArray[address, MAX_COINS]: coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) base_pool: address = self.pool_data[_pool].base_pool assert base_pool != empty(address) # dev: pool is not metapool - coins[0] = self.pool_data[_pool].coins[0] + + coins.append(self.pool_data[_pool].coins[0]) + base_pool_n_coins: uint256 = len(self.base_pool_data[base_pool].coins) for i in range(1, MAX_COINS): - coins[i] = self.base_pool_data[base_pool].coins[i - 1] - if coins[i] == empty(address): + if i - 1 == base_pool_n_coins: break + coins.append(self.base_pool_data[base_pool].coins[i - 1]) + return coins @@ -440,7 +437,7 @@ def is_meta(_pool: address) -> bool: def get_pool_asset_types(_pool: address) -> DynArray[uint8, MAX_COINS]: """ @notice Query the asset type of `_pool` - @dev 0 = USD, 1 = ETH, 2 = BTC, 3 = Other + @dev 0 = Plain, 1 = Oracle, 2 = Rebasing @param _pool Pool Address @return Integer indicating the pool asset type """ @@ -481,7 +478,7 @@ def deploy_plain_pool( Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_types Asset types for pool, as an integer - 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING + 0 = Plain, 1 = Oracle, 2 = Rebasing @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @@ -523,7 +520,6 @@ def deploy_plain_pool( _A, # _A: uint256 _fee, # _fee: uint256 _ma_exp_time, # _ma_exp_time: uint256 - WETH20, # _weth: address _coins, # _coins: DynArray[address, MAX_COINS] _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] _asset_types, # _asset_types: DynArray[uint8, MAX_COINS] @@ -606,7 +602,7 @@ def deploy_metapool( Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_type Asset type for token, as an integer - 0 = PLAIN, 1 = ETH, 2 = ORACLE, 3 = REBASING + 0 = Plain, 1 = Oracle, 2 = Rebasing @param _method_id First four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @@ -727,7 +723,7 @@ def add_base_pool( @notice Add a base pool to the registry, which may be used in factory metapools @dev Only callable by admin @param _base_pool Pool address to add - @param _asset_types Asset type for pool, as an integer 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing + @param _asset_types Asset type for pool, as an integer 0 = Plain, 1 = Oracle, 2 = Rebasing """ assert msg.sender == self.admin # dev: admin-only function assert len(self.base_pool_data[_base_pool].coins) == 0 # dev: pool exists diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index f7921cd7..1ddc741c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -10,9 +10,8 @@ nth coin index of the base pool. Asset Types: 0. Basic ERC20 token with no additional features - 1. WETH - can we directly converted to/from ETH - 2. Oracle - token with rate oracle - 3. Rebasing - token with rebase (e.g. stETH) + 1. Oracle - token with rate oracle + 2. Rebasing - token with rebase (e.g. stETH) Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -28,9 +27,9 @@ b. swaps without transferFrom (no need for token approvals) 3. Adds feature: `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. - Note: a. If pool contains rebasing tokens and one of the `asset_types` is 3 (Rebasing) + Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. - b. If pool contains rebasing token and `asset_types` does not contain 3 (Rebasing) + b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output @@ -202,7 +201,7 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -admin_fee: constant(uint256) = 5000000000 +admin_fee: public(constant(uint256)) = 5000000000 MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -386,7 +385,7 @@ def _transfer_in( if expect_optimistic_transfer: - assert _incoming_coin_asset_type != 3 # dev: rebasing coins not supported + assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -411,7 +410,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if _incoming_coin_asset_type == 3: + if _incoming_coin_asset_type == 2: assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! else: assert dx == _dx # dev: pool did not receive tokens for swap @@ -517,8 +516,6 @@ def exchange( """ @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method - Allows for native token swaps (e.g. ETH <> whatever) - If native token is not in coin list and msg.value > 0, swap will revert @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -552,8 +549,8 @@ def exchange_extended( """ @notice Perform an exchange between two coins after a callback @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth). Users of this method are dex aggregators, - arbitrageurs, or other users who do not wish to grant approvals to the contract. + Users of this method are dex aggregators,arbitrageurs, or other + users who do not wish to grant approvals to the contract. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -1091,7 +1088,7 @@ def _exchange_underlying( if input_coin == BASE_COINS[base_i]: - assert asset_types[base_i + 2] != 3 # dev: rebasing coins not supported + assert asset_types[base_i + 2] != 2 # dev: rebasing coins not supported # we expect base_coin's balance to be 0. So swap whatever base_coin's # balance the pool has: @@ -1099,7 +1096,7 @@ def _exchange_underlying( else: - assert asset_types[i] != 3 # dev: rebasing coins not supported + assert asset_types[i] != 2 # dev: rebasing coins not supported # Since the coin belongs to the metapool, we need to check if we got more than # what already exists in the pool (since last record): diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index e6ac859b..0ba6a0c2 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -10,9 +10,8 @@ 1, 2 and 3 relative to coin 0. @dev Asset Types: 0. Basic ERC20 token with no additional features - 1. WETH - can we directly converted to/from ETH - 2. Oracle - token with rate oracle - 3. Rebasing - token with rebase (e.g. stETH) + 1. Oracle - token with rate oracle + 2. Rebasing - token with rebase (e.g. stETH) Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -23,20 +22,19 @@ `exchange_received`. 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. - 3. Support for ETH/WETH transfers - 4. Adds oracles based on AMM State Price (and _not_ last traded price). - 5. Adds exchanging tokens with callbacks that allows for: + 3. Adds oracles based on AMM State Price (and _not_ last traded price). + 4. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 6. Adds feature: `exchange_received`, which is inspired + 5. Adds feature: `exchange_received`, which is inspired by Uniswap V2: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. - Note: a. If pool contains rebasing tokens and one of the `asset_types` is 3 (Rebasing) + Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. - b. If pool contains rebasing token and `asset_types` does not contain 3 (Rebasing) + b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 7. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 6. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. """ @@ -52,10 +50,6 @@ interface Factory: def admin() -> address: view def views_implementation() -> address: view -interface WETH: - def deposit(): payable - def withdraw(_amount: uint256): nonpayable - interface ERC1271: def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view @@ -140,7 +134,6 @@ MAX_COINS_128: constant(int128) = 8 # ---------------------------- Pool Variables -------------------------------- -WETH20: immutable(address) N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 @@ -166,7 +159,7 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -admin_fee: constant(uint256) = 5000000000 +admin_fee: public(constant(uint256)) = 5000000000 MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -218,7 +211,6 @@ def __init__( _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _weth: address, _coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], _asset_types: DynArray[uint8, MAX_COINS], @@ -248,7 +240,6 @@ def __init__( @param _oracles Array of rate oracle addresses. """ - WETH20 = _weth coins = _coins __n_coins: uint256 = len(_coins) N_COINS = __n_coins @@ -278,10 +269,6 @@ def __init__( if i == N_COINS_128: break - # Enforce native token as coin[0] - if _coins[i] == WETH20: - assert i == 0, "ETH must be at index 0" - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) # --------------------------- initialize storage --------------------------- @@ -355,7 +342,7 @@ def _transfer_in( if expect_optimistic_transfer: - assert _incoming_coin_asset_type != 3 # dev: rebasing coins not supported + assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] elif callback_sig != empty(bytes32): @@ -380,7 +367,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if _incoming_coin_asset_type == 3: + if _incoming_coin_asset_type == 2: assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! else: assert dx == _dx # dev: pool did not receive tokens for swap diff --git a/tests/conftest.py b/tests/conftest.py index 1abf06e9..fb4b57a1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ ] pool_types = {"basic": 0, "meta": 1} -token_types = {"plain": 0, "eth": 1, "oracle": 2, "rebasing": 3} +token_types = {"plain": 0, "oracle": 1, "rebasing": 2} return_types = {"revert": 0, "False": 1, "None": 2} @@ -34,7 +34,7 @@ def pytest_addoption(parser): parser.addoption( "--token-types", action="store", - default="plain,eth,oracle,rebasing", + default="plain,oracle,rebasing", help="comma-separated list of ERC20 token types to test against", ) parser.addoption( @@ -54,9 +54,6 @@ def pytest_addoption(parser): def pytest_generate_tests(metafunc): pool_size = int(metafunc.config.getoption("pool_size")) - # TODO: remove after adding implementations - # assert pool_size == 2, "Only 2-coin pools supported" - if "pool_size" in metafunc.fixturenames: metafunc.parametrize( "pool_size", @@ -80,12 +77,7 @@ def pytest_generate_tests(metafunc): cli_options.remove("eth") cli_options = ["eth"] + cli_options - combinations = list(itertools.combinations(cli_options, pool_size)) - if pool_size == 2: - # do not include (eth,eth) pair - for t in cli_options: - if t != "eth": - combinations.append((t, t)) + combinations = list(itertools.combinations_with_replacement(cli_options, pool_size)) metafunc.parametrize( "pool_token_types", @@ -102,7 +94,7 @@ def pytest_generate_tests(metafunc): "metapool_token_type", [token_types[c] for c in cli_options], indirect=True, - ids=[f"(PoolTokenTypes={c})" for c in cli_options], + ids=[f"(MetaTokenType={c})" for c in cli_options], ) if "initial_decimals" in metafunc.fixturenames: @@ -158,14 +150,14 @@ def initial_decimals(request): @pytest.fixture(scope="session") def decimals(initial_decimals, pool_token_types): - # eth and oracle tokens are always 18 decimals - return [d if t in [0, 3] else 18 for d, t in zip(initial_decimals, pool_token_types)] + # oracle tokens are always 18 decimals + return [d if t != 1 else 18 for d, t in zip(initial_decimals, pool_token_types)] @pytest.fixture(scope="session") def meta_decimals(initial_decimals, metapool_token_type, decimals): - # eth and oracle tokens are always 18 decimals - return decimals[0] if metapool_token_type in [0, 3] else 18 + # oracle tokens are always 18 decimals + return decimals[0] if metapool_token_type != 1 else 18 # Usage diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index d5cceb54..d01284a4 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -89,7 +89,7 @@ def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) @@ -102,3 +102,89 @@ def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): @pytest.fixture(scope="function") def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) + + +# <--------------------- Functions ---------------------> +def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = 1_000_000 // 3 + 1 + with boa.env.prank(user): + for d, token in zip(base_pool_decimals, base_pool_tokens): + token._mint_for_testing(user, amount * 10**d) + token.approve(base_pool.address, 2**256 - 1) + + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) + + +@pytest.fixture(scope="function") +def add_initial_liquidity_owner( + owner, + approve_owner, + mint_owner, + deposit_amounts, + swap, + pool_type, + underlying_tokens, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, +): + if pool_type == 0: + with boa.env.prank(owner): + swap.add_liquidity(deposit_amounts, 0) + else: + add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(owner): + base_pool_lp_token.approve(swap.address, 2**256 - 1) + lp_token_bal = base_pool_lp_token.balanceOf(owner) + to_mint_token0 = lp_token_bal * 10 ** underlying_tokens[0].decimals() // 10 ** base_pool_lp_token.decimals() + + mint_for_testing(owner, to_mint_token0, underlying_tokens[0], False) + underlying_tokens[0].approve(swap.address, 2**256 - 1) + + swap.add_liquidity([to_mint_token0, lp_token_bal], 0) + + +@pytest.fixture(scope="function") +def add_initial_liquidity_alice( + alice, + approve_alice, + mint_alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, +): + if pool_type == 0: + with boa.env.prank(alice): + swap.add_liquidity(deposit_amounts, 0) + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(alice): + base_pool_lp_token.approve(swap.address, 2**256 - 1) + swap.add_liquidity(deposit_amounts, 0) + + +@pytest.fixture(scope="function") +def mint_meta_bob( + bob, + mint_bob, + base_pool, + base_pool_tokens, + base_pool_decimals, + underlying_tokens, + initial_amounts, + base_pool_lp_token, +): + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) + + +@pytest.fixture(scope="function") +def approve_meta_bob(bob, underlying_tokens, swap): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index d25e27a0..6e552e66 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -52,7 +52,6 @@ def factory( deployer, fee_receiver, owner, - weth, gauge_implementation, views_implementation, math_implementation, @@ -62,7 +61,6 @@ def factory( "contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner, - weth, ) with boa.env.prank(owner): diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 40fcc017..0674ef6d 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,14 +2,11 @@ import pytest from eth_utils import function_signature_to_4byte_selector -from tests.utils.tokens import mint_for_testing - @pytest.fixture(scope="module") def swap( deployer, factory, - weth, pool_size, pool_type, pool_token_types, @@ -40,16 +37,12 @@ def swap( A = 1000 fee = 3000000 asset_types.append(1) - elif t == 2: - A = 1000 - fee = 3000000 - asset_types.append(2) method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address - elif t == 3: + elif t == 2: A = 500 fee = 4000000 - asset_types.append(3) + asset_types.append(2) with boa.env.prank(deployer): pool = factory.deploy_plain_pool( @@ -73,17 +66,13 @@ def swap( A = 1000 fee = 3000000 asset_type = 1 - elif metapool_token_type == 2: - A = 1000 - fee = 3000000 - asset_type = 2 method_id = oracle_method_id oracle = underlying_tokens[0].address - elif metapool_token_type == 3: + elif metapool_token_type == 2: A = 500 fee = 4000000 - asset_type = 3 + asset_type = 2 pool = factory.deploy_metapool( base_pool.address, # _base_pool: address @@ -120,90 +109,3 @@ def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base ) base_pool_lp_token.set_minter(base_pool.address) return base_pool - - -# <--------------------- Functions ---------------------> -@pytest.fixture(scope="module") -def is_eth_pool(pool_tokens, weth): - return weth in pool_tokens - - -def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = 1_000_000 - with boa.env.prank(user): - for d, token in zip(base_pool_decimals, base_pool_tokens): - token._mint_for_testing(user, amount * 10**d) - token.approve(base_pool.address, 2**256 - 1) - - amounts = [amount * 10**d for d in base_pool_decimals] - base_pool.add_liquidity(amounts, 0) - - -@pytest.fixture(scope="function") -def add_initial_liquidity_owner( - owner, - approve_owner, - mint_owner, - deposit_amounts, - swap, - pool_type, - underlying_tokens, - base_pool, - base_pool_tokens, - base_pool_decimals, - base_pool_lp_token, -): - if pool_type == 0: - with boa.env.prank(owner): - swap.add_liquidity(deposit_amounts, 0) - else: - add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) - with boa.env.prank(owner): - base_pool_lp_token.approve(swap.address, 2**256 - 1) - lp_token_bal = base_pool_lp_token.balanceOf(owner) - to_mint_token0 = lp_token_bal * 10 ** underlying_tokens[0].decimals() // 10 ** base_pool_lp_token.decimals() - - mint_for_testing(owner, to_mint_token0, underlying_tokens[0], False) - underlying_tokens[0].approve(swap.address, 2**256 - 1) - - swap.add_liquidity([to_mint_token0, lp_token_bal], 0) - - -@pytest.fixture(scope="function") -def add_initial_liquidity_alice( - alice, - approve_alice, - mint_alice, - deposit_amounts, - swap, - pool_type, - base_pool, - base_pool_tokens, - base_pool_decimals, - base_pool_lp_token, -): - if pool_type == 0: - with boa.env.prank(alice): - swap.add_liquidity(deposit_amounts, 0) - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - with boa.env.prank(alice): - base_pool_lp_token.approve(swap.address, 2**256 - 1) - swap.add_liquidity(deposit_amounts, 0) - - -@pytest.fixture(scope="function") -def mint_meta_bob( - bob, - mint_bob, - base_pool, - base_pool_tokens, - base_pool_decimals, -): - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - - -@pytest.fixture(scope="function") -def approve_meta_bob(bob, underlying_tokens, swap): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 0874f908..cdfcbd3a 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -11,12 +11,6 @@ def plain_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") -def weth(deployer): - with boa.env.prank(deployer): - return boa.load("contracts/mocks/WETH.vy") - - @pytest.fixture(scope="module") def oracle_tokens(deployer, decimals): tokens = [] @@ -52,16 +46,14 @@ def rebase_tokens(deployer, decimals): @pytest.fixture(scope="module") -def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_tokens): +def pool_tokens(pool_token_types, plain_tokens, oracle_tokens, rebase_tokens): pool_tokens = [] for i, t in enumerate(pool_token_types): if t == 0: pool_tokens.append(plain_tokens[i]) elif t == 1: - pool_tokens.append(weth) - elif t == 2: pool_tokens.append(oracle_tokens[i]) - elif t == 3: + elif t == 2: pool_tokens.append(rebase_tokens[i]) else: raise ValueError("Wrong pool token type") @@ -71,14 +63,12 @@ def pool_tokens(pool_token_types, plain_tokens, weth, oracle_tokens, rebase_toke # <--------------------- Metapool configuration ---------------------> @pytest.fixture(scope="module") -def metapool_token(metapool_token_type, plain_tokens, weth, oracle_tokens, rebase_tokens): +def metapool_token(metapool_token_type, plain_tokens, oracle_tokens, rebase_tokens): if metapool_token_type == 0: return plain_tokens[0] elif metapool_token_type == 1: - return weth - elif metapool_token_type == 2: return oracle_tokens[0] - elif metapool_token_type == 3: + elif metapool_token_type == 2: return rebase_tokens[0] else: raise ValueError("Wrong pool token type") diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 9c017178..4eb4ca66 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -3,7 +3,7 @@ import boa import pytest -from tests.fixtures.pools import add_base_pool_liquidity +from tests.fixtures.accounts import add_base_pool_liquidity from tests.utils.tokens import mint_for_testing SWAP_AMOUNT = 50 @@ -109,7 +109,7 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): # TODO: need to permutate/combinate N_COIN combos. -@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_nonrebasing( bob, @@ -133,7 +133,7 @@ def test_exchange_received_nonrebasing( assert swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] == swap_data["amount_out"] -@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_not_received( bob, swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner @@ -143,7 +143,7 @@ def test_exchange_not_received( swap.exchange_received(sending, receiving, 1, 0, bob) -@pytest.mark.only_for_token_types(3) +@pytest.mark.only_for_token_types(2) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts( bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner @@ -154,7 +154,7 @@ def test_exchange_received_rebasing_reverts( @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_nonrebasing( bob, @@ -191,7 +191,7 @@ def test_exchange_underlying_received_nonrebasing( @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(0, 1, 2) +@pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_not_received( bob, swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner @@ -202,7 +202,7 @@ def test_exchange_underlying_not_received( @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(3) +@pytest.mark.only_for_token_types(2) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts( swap, transfer_and_swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner diff --git a/tests/test_factory.py b/tests/test_factory.py index 772ca703..4ba32cb8 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -128,7 +128,38 @@ def test_is_meta(self, factory, swap): assert factory.is_meta(swap.address) is True class TestFactoryAddPools: - def test_add_base_pool(self, factory, owner, add_base_pool, forked_chain): + @pytest.fixture + def empty_factory(self, deployer, fee_receiver, owner): + with boa.env.prank(deployer): + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + owner, + ) + return _factory + + @pytest.fixture + def empty_factory_with_implementations( + self, + empty_factory, + owner, + gauge_implementation, + views_implementation, + math_implementation, + amm_implementation, + amm_implementation_meta, + ): + with boa.env.prank(owner): + empty_factory.set_gauge_implementation(gauge_implementation.address) + empty_factory.set_views_implementation(views_implementation.address) + empty_factory.set_math_implementation(math_implementation.address) + + empty_factory.set_pool_implementations(0, amm_implementation.address) + empty_factory.set_metapool_implementations(0, amm_implementation_meta.address) + + return empty_factory + + def test_add_base_pool(self, empty_factory, owner, forked_chain): susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" coins = [ @@ -138,10 +169,10 @@ def test_add_base_pool(self, factory, owner, add_base_pool, forked_chain): "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", ] - assert factory.base_pool_count() == 1 - factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) - assert factory.base_pool_count() == 2 - assert factory.base_pool_list(1) == susd_pool + assert empty_factory.base_pool_count() == 0 + empty_factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) + assert empty_factory.base_pool_count() == 1 + assert empty_factory.base_pool_list(0) == susd_pool def test_add_base_pool_already_exists( self, @@ -181,9 +212,9 @@ def test_add_base_pool_only_admin( ) def test_deploy_plain_pool( - self, factory, amm_interface, set_pool_implementations, pool_tokens, pool_size, zero_address + self, empty_factory_with_implementations, amm_interface, pool_tokens, pool_size, zero_address ): - swap_address = factory.deploy_plain_pool( + swap_address = empty_factory_with_implementations.deploy_plain_pool( "test", "test", [t.address for t in pool_tokens], @@ -204,13 +235,13 @@ def test_deploy_plain_pool( assert swap.A() == 2000 assert swap.fee() == 1000000 - assert factory.pool_count() == 1 - assert factory.pool_list(0) == swap.address - assert factory.get_decimals(swap) == [t.decimals() for t in pool_tokens] + assert empty_factory_with_implementations.pool_count() == 1 + assert empty_factory_with_implementations.pool_list(0) == swap.address + assert empty_factory_with_implementations.get_decimals(swap) == [t.decimals() for t in pool_tokens] def test_pool_count( self, - factory, + empty_factory_with_implementations, swap, add_base_pool, amm_interface, @@ -219,9 +250,9 @@ def test_pool_count( pool_size, zero_address, ): - assert factory.pool_count() == 1 + assert empty_factory_with_implementations.pool_count() == 0 - _ = factory.deploy_plain_pool( + empty_factory_with_implementations.deploy_plain_pool( "test", "test", [t.address for t in pool_tokens], @@ -233,4 +264,4 @@ def test_pool_count( [bytes(b"")] * pool_size, [zero_address] * pool_size, ) - assert factory.pool_count() == 2 + assert empty_factory_with_implementations.pool_count() == 1 From dc880d66f106ed1218d5083c0b2cc230f7aa38a2 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:05:34 +0200 Subject: [PATCH 118/337] fix bob's balances --- tests/fixtures/accounts.py | 17 ++++++++++------- tests/fixtures/constants.py | 2 +- tests/fixtures/mocks.py | 2 +- tests/pools/test_exchange_received.py | 17 +++++------------ 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index d01284a4..6b8faa1b 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -5,6 +5,8 @@ from tests.utils.tokens import mint_for_testing +from .constants import INITIAL_AMOUNT + @pytest.fixture(scope="module") def deployer() -> AddressType: @@ -31,32 +33,32 @@ def alice(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def bob(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def charlie(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def dave(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def erin(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def frank(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def accounts(bob, charlie, dave, erin, frank): return [bob, charlie, dave, erin, frank] @@ -106,7 +108,7 @@ def approve_bob(bob, pool_tokens, swap): # <--------------------- Functions ---------------------> def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = 1_000_000 // 3 + 1 + amount = INITIAL_AMOUNT // 3 with boa.env.prank(user): for d, token in zip(base_pool_decimals, base_pool_tokens): token._mint_for_testing(user, amount * 10**d) @@ -182,6 +184,7 @@ def mint_meta_bob( ): add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) + assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) @pytest.fixture(scope="function") diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 7b100cf6..9f2360a9 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -1,6 +1,6 @@ import pytest -INITIAL_AMOUNT = 1_000_000 +INITIAL_AMOUNT = 3_000_000 @pytest.fixture(scope="module") diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py index 7555d1cb..2046c031 100644 --- a/tests/fixtures/mocks.py +++ b/tests/fixtures/mocks.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def callback_contract(bob, swap, pool_tokens, underlying_tokens): with boa.env.prank(bob): diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 4eb4ca66..a0a921dd 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -3,10 +3,9 @@ import boa import pytest -from tests.fixtures.accounts import add_base_pool_liquidity -from tests.utils.tokens import mint_for_testing +from tests.fixtures.constants import INITIAL_AMOUNT -SWAP_AMOUNT = 50 +SWAP_AMOUNT = INITIAL_AMOUNT // 1000 @pytest.fixture(scope="function") @@ -58,16 +57,9 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): # calc amount in: amount_in = SWAP_AMOUNT * 10 ** (input_coin.decimals()) - # mint tokens if account does not have: - if input_coin.balanceOf(bob) < amount_in: - if input_coin == base_pool_lp_token: - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - else: - mint_for_testing(bob, amount_in, input_coin, False) - # record balances before - bob_sending_balance_before = input_coin.balanceOf(bob) - bob_receiving_balance_before = output_coin.balanceOf(bob) + bob_sending_balance_before = input_coin.balanceOf(bob) # always INITIAL_AMOUNT + bob_receiving_balance_before = output_coin.balanceOf(bob) # always INITIAL_AMOUNT pool_sending_balance_before = input_coin.balanceOf(pool.address) pool_receiving_balance_before = output_coin.balanceOf(pool.address) base_pool_sending_balance_before = input_coin.balanceOf(base_pool.address) @@ -125,6 +117,7 @@ def test_exchange_received_nonrebasing( transfer_and_swap, ): swap_data = transfer_and_swap(swap, sending, receiving, False) + print(swap_data) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] assert swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] == swap_data["amount_out"] From 25c6c4057dd9abbf24546d7e39fd380f611eba66 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 22 Jul 2023 16:58:52 +0200 Subject: [PATCH 119/337] fix some tests and fixtures; refactor contract to reduce redundant code --- contracts/main/CurveStableSwapFactoryNG.vy | 16 ++--- contracts/main/CurveStableSwapMetaNG.vy | 83 +++++++++------------- contracts/main/CurveStableSwapNG.vy | 6 +- contracts/mocks/ERC20.vy | 3 + contracts/mocks/ERC20Oracle.vy | 3 + contracts/mocks/ERC20Rebasing.vy | 3 + poetry.lock | 6 +- pyproject.toml | 2 +- tests/conftest.py | 5 +- tests/fixtures/pools.py | 30 ++++---- tests/pools/test_exchange_received.py | 9 ++- tests/utils/tokens.py | 2 +- 12 files changed, 86 insertions(+), 82 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 2f972873..7fbcbab5 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -454,10 +454,10 @@ def deploy_plain_pool( _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _implementation_idx: uint256 = 0, - _asset_types: DynArray[uint8, MAX_COINS] = empty(DynArray[uint8, MAX_COINS]), - _method_ids: DynArray[bytes4, MAX_COINS] = empty(DynArray[bytes4, MAX_COINS]), - _oracles: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]), + _implementation_idx: uint256, + _asset_types: DynArray[uint8, MAX_COINS], + _method_ids: DynArray[bytes4, MAX_COINS], + _oracles: DynArray[address, MAX_COINS], ) -> address: """ @notice Deploy a new plain pool @@ -573,10 +573,10 @@ def deploy_metapool( _A: uint256, _fee: uint256, _ma_exp_time: uint256, - _implementation_idx: uint256 = 0, - _asset_type: uint8 = 0, - _method_id: bytes4 = empty(bytes4), - _oracle: address = empty(address), + _implementation_idx: uint256, + _asset_type: uint8, + _method_id: bytes4, + _oracle: address, ) -> address: """ @notice Deploy a new metapool diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 1ddc741c..9372e257 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -349,7 +349,8 @@ def __init__( @internal def _transfer_in( - coin_idx: int128, + coin_metapool_idx: int128, + coin_basepool_idx: int128, dx: uint256, dy: uint256, callbacker: address, @@ -378,15 +379,24 @@ def _transfer_in( @params receiver address to transfer `_coin` to. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ - _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - _incoming_coin_asset_type: uint8 = asset_types[coin_idx] + _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) + _incoming_coin_asset_type: uint8 = asset_types[coin_metapool_idx] + _stored_balance: uint256 = self.stored_balances[coin_metapool_idx] + + if coin_basepool_idx >= 0 and coin_metapool_idx == 1: # self._exchange_underlying + + _input_coin = ERC20(BASE_COINS[coin_basepool_idx]) + _incoming_coin_asset_type = asset_types[coin_basepool_idx + 2] + _stored_balance = 0 + + _dx: uint256 = _input_coin.balanceOf(self) # ------------------------- Handle Transfers ----------------------------- if expect_optimistic_transfer: assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported - _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] + _dx -= _stored_balance # <--- for base_pool coins, stored balance is 0. elif callback_sig != empty(bytes32): @@ -394,19 +404,16 @@ def _transfer_in( callbacker, concat( slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coins[coin_idx], dx, dy) + _abi_encode(sender, receiver, _input_coin.address, dx, dy) ) ) - _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + _dx = _input_coin.balanceOf(self) - _dx else: - assert ERC20(coins[coin_idx]).transferFrom( - sender, self, dx, default_return_value=True - ) - - _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + assert _input_coin.transferFrom(sender, self, dx, default_return_value=True) + _dx = _input_coin.balanceOf(self) - _dx # --------------------------- Check Transfer ----------------------------- @@ -417,7 +424,7 @@ def _transfer_in( # ----------------------- Update Stored Balances ------------------------- - self.stored_balances[coin_idx] += _dx + self.stored_balances[coin_metapool_idx] += _dx return _dx @@ -734,6 +741,7 @@ def add_liquidity( new_balances[i] += self._transfer_in( i, + -1, # <--- we're not handling underlying coins here _amounts[i], 0, empty(address), @@ -1011,6 +1019,7 @@ def _exchange( # `dx` is whatever the pool received after ERC20 transfer: dx: uint256 = self._transfer_in( i, + -1, # <----- we're not handling underlying coins here. _dx, _min_dy, callbacker, @@ -1066,6 +1075,8 @@ def _exchange_underlying( input_coin: address = empty(address) output_coin: address = empty(address) + # ------------------------ Determine coin indices ------------------------ + if i == 0: input_coin = coins[0] else: @@ -1081,42 +1092,17 @@ def _exchange_underlying( # --------------------------- Do Transfer in ----------------------------- - dx_w_fee: uint256 = 0 - - # for exchange_underlying, optimistic transfers need to be handled differently - if expect_optimistic_transfer: - - if input_coin == BASE_COINS[base_i]: - - assert asset_types[base_i + 2] != 2 # dev: rebasing coins not supported - - # we expect base_coin's balance to be 0. So swap whatever base_coin's - # balance the pool has: - dx_w_fee = ERC20(input_coin).balanceOf(self) - - else: - - assert asset_types[i] != 2 # dev: rebasing coins not supported - - # Since the coin belongs to the metapool, we need to check if we got more than - # what already exists in the pool (since last record): - dx_w_fee = ERC20(input_coin).balanceOf(self) - self.stored_balances[meta_i] - self.stored_balances[meta_i] += dx_w_fee - - assert dx_w_fee == _dx, "Pool did not receive tokens for swap" - - else: - - dx_w_fee = self._transfer_in( - i, - _dx, - _min_dy, - callbacker, - callback_sig, - sender, - receiver, - False, # expect_optimistic_transfer = False - ) + dx_w_fee: uint256 = self._transfer_in( + meta_i, + base_i, + _dx, + _min_dy, + callbacker, + callback_sig, + sender, + receiver, + expect_optimistic_transfer, + ) # ------------------------------- Exchange ------------------------------- @@ -1138,6 +1124,7 @@ def _exchange_underlying( dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) # Adjust stored balances of meta-level tokens: + # TODO: refactor this stray self.stored_balances to self._transfer_out somehow self.stored_balances[meta_j] -= dy # Withdraw from the base pool if needed diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 0ba6a0c2..ee91f8e6 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -142,7 +142,7 @@ factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] fee: public(uint256) # fee * 1e10 -asset_types: DynArray[uint8, MAX_COINS] +asset_types: immutable(DynArray[uint8, MAX_COINS]) FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -251,7 +251,7 @@ def __init__( self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) rate_multipliers = _rate_multipliers - self.asset_types = _asset_types + asset_types = _asset_types factory = Factory(msg.sender) @@ -336,7 +336,7 @@ def _transfer_in( @params expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - _incoming_coin_asset_type: uint8 = self.asset_types[coin_idx] + _incoming_coin_asset_type: uint8 = asset_types[coin_idx] # ------------------------- Handle Transfers ----------------------------- diff --git a/contracts/mocks/ERC20.vy b/contracts/mocks/ERC20.vy index fc1156e5..efeee386 100644 --- a/contracts/mocks/ERC20.vy +++ b/contracts/mocks/ERC20.vy @@ -24,6 +24,9 @@ balanceOf: public(HashMap[address, uint256]) allowances: HashMap[address, HashMap[address, uint256]] totalSupply: public(uint256) +# asset type +asset_type: public(constant(uint8)) = 0 + @external def __init__(_name: String[64], _symbol: String[32], _decimals: uint256): diff --git a/contracts/mocks/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy index dcef58d6..24e62b6e 100644 --- a/contracts/mocks/ERC20Oracle.vy +++ b/contracts/mocks/ERC20Oracle.vy @@ -27,6 +27,9 @@ totalSupply: public(uint256) exchange_rate: public(uint256) +# asset type +asset_type: public(constant(uint8)) = 1 + @external def __init__( diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index bfb8138d..b41576f7 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -36,6 +36,9 @@ totalShares: public(uint256) shares: public(HashMap[address, uint256]) IS_UP: immutable(bool) +# asset type +asset_type: public(constant(uint8)) = 2 + @external def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, is_up: bool): diff --git a/poetry.lock b/poetry.lock index 241955aa..fccd71c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2720,8 +2720,8 @@ forking-recommended = ["plyvel", "ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "bd4af917754e7a66a84dee6134c8a42112224471" -resolved_reference = "bd4af917754e7a66a84dee6134c8a42112224471" +reference = "e29a70640b67c3e87c248a582454ae6fe8eeec00" +resolved_reference = "e29a70640b67c3e87c248a582454ae6fe8eeec00" [[package]] name = "tomli" @@ -3014,4 +3014,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "1637c74047db6fa1232614ff15ff07f6bc9e5eebccb13c46e31d87da415e1eb7" +content-hash = "9c10197ea9e8028bd48145b237b3af2216b57495ef714f0fc27dd602295679ca" diff --git a/pyproject.toml b/pyproject.toml index 9f26cd44..143cde9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "bd4af917754e7a66a84dee6134c8a42112224471"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "e29a70640b67c3e87c248a582454ae6fe8eeec00"} vyper = "^0.3.9" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/conftest.py b/tests/conftest.py index fb4b57a1..dc7b62f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -169,10 +169,11 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals): # @pytest.mark.only_for_token_types(2) # class TestPoolsWithOracleToken: @pytest.fixture(autouse=True) -def skip_by_token_type(request, pool_token_types): +def skip_by_token_type(request, swap): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: - if not all(pool_token_type in only_for_token_types.args for pool_token_type in pool_token_types): + asset_types = swap._immutables.asset_types + if not all(asset_type in only_for_token_types.args for asset_type in asset_types): pytest.skip("skipped because no tokens for these types") diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 0674ef6d..0939f473 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -10,6 +10,7 @@ def swap( pool_size, pool_type, pool_token_types, + metapool_token_type, pool_tokens, zero_address, amm_interface, @@ -28,21 +29,24 @@ def swap( oracles = [zero_address] * pool_size asset_types = [] - for i, t in enumerate(pool_token_types): - if t == 0: + for i in range(len(pool_tokens)): + + asset_type = pool_tokens[i].asset_type() + + if asset_type == 0: A = 2000 fee = 1000000 - asset_types.append(0) - elif t == 1: + asset_types.append(asset_type) + elif asset_type == 1: A = 1000 fee = 3000000 - asset_types.append(1) + asset_types.append(asset_type) method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address - elif t == 2: + elif asset_type == 2: A = 500 fee = 4000000 - asset_types.append(2) + asset_types.append(asset_type) with boa.env.prank(deployer): pool = factory.deploy_plain_pool( @@ -55,8 +59,8 @@ def swap( fee = 1000000 method_id = bytes(b"") oracle = zero_address - asset_type = 0 # 0 = Plain, 1 = ETH, 2 = Oracle, 3 = Rebasing - metapool_token_type = pool_token_types[0] + asset_type = 0 # 0 = Plain, 1 = Oracle, 2 = Rebasing + metapool_token_type = underlying_tokens[0].asset_type() if metapool_token_type == 0: A = 2000 @@ -82,10 +86,10 @@ def swap( A, # _A: uint256, fee, # _fee: uint256, 866, # _ma_exp_time: uint256, - 0, # _implementation_idx: uint256 = 0, - asset_type, # _asset_type: uint8 = 0, - method_id, # _method_id: bytes4 = empty(bytes4), - oracle, # _oracle: address = empty(address), + 0, # _implementation_idx: uint256 + metapool_token_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address ) return amm_interface_meta.at(pool) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index a0a921dd..4c223016 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -4,6 +4,7 @@ import pytest from tests.fixtures.constants import INITIAL_AMOUNT +from tests.utils.tokens import mint_for_testing SWAP_AMOUNT = INITIAL_AMOUNT // 1000 @@ -57,6 +58,9 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): # calc amount in: amount_in = SWAP_AMOUNT * 10 ** (input_coin.decimals()) + if amount_in > input_coin.balanceOf(bob): + mint_for_testing(bob, amount_in, input_coin, False) + # record balances before bob_sending_balance_before = input_coin.balanceOf(bob) # always INITIAL_AMOUNT bob_receiving_balance_before = output_coin.balanceOf(bob) # always INITIAL_AMOUNT @@ -117,7 +121,6 @@ def test_exchange_received_nonrebasing( transfer_and_swap, ): swap_data = transfer_and_swap(swap, sending, receiving, False) - print(swap_data) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] assert swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] == swap_data["amount_out"] @@ -142,7 +145,7 @@ def test_exchange_received_rebasing_reverts( bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner ): - with boa.reverts(compiler="external call failed"): + with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) @@ -202,5 +205,5 @@ def test_exchange_underlying_received_rebasing_reverts( ): if sending == 0: - with boa.reverts(compiler="external call failed"): + with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) diff --git a/tests/utils/tokens.py b/tests/utils/tokens.py index e89880be..edf7f279 100644 --- a/tests/utils/tokens.py +++ b/tests/utils/tokens.py @@ -3,7 +3,7 @@ from eth_utils import to_checksum_address -def mint_for_testing(user: str, amount, token_contract: VyperContract | None, mint_eth: bool = False) -> None: +def mint_for_testing(user: str, amount: int, token_contract: VyperContract | None, mint_eth: bool = False) -> None: assert token_contract is not None or mint_eth user = to_checksum_address(user) From 85e917387105d09a099b49ca2606553d11222d1c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 25 Jul 2023 13:11:04 +0200 Subject: [PATCH 120/337] add dynamic fees to metapool implementation --- contracts/main/CurveStableSwapMetaNG.vy | 139 +++++++++++++++++------ contracts/main/CurveStableSwapNGViews.vy | 6 + 2 files changed, 112 insertions(+), 33 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9372e257..e3a72869 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -162,6 +162,7 @@ event StopRampA: event ApplyNewFee: fee: uint256 + offpeg_fee_multiplier: uint256 MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory @@ -174,7 +175,6 @@ N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -# To denote that it is a plain pool: BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) @@ -183,10 +183,14 @@ math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] -fee: public(uint256) # fee * 1e10 asset_types: immutable(DynArray[uint8, MAX_COINS]) +# Fee specific vars FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +fee: public(uint256) # fee * 1e10 +offpeg_fee_multiplier: public(uint256) # * 1e10 +admin_fee: public(constant(uint256)) = 5000000000 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 # ---------------------- Pool Amplification Parameters ----------------------- @@ -201,8 +205,6 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -admin_fee: public(constant(uint256)) = 5000000000 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -252,6 +254,7 @@ def __init__( _symbol: String[10], _A: uint256, _fee: uint256, + _offpeg_fee_multiplier: uint256, _ma_exp_time: uint256, _math_implementation: address, _base_pool: address, @@ -308,6 +311,7 @@ def __init__( self.initial_A = A self.future_A = A self.fee = _fee + self.offpeg_fee_multiplier = _offpeg_fee_multiplier assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time @@ -768,20 +772,35 @@ def add_liquidity( if total_supply > 0: + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 + + ys: uint256 = (D0 + D1) / N_COINS + xs: uint256 = 0 + _dynamic_fee_i: uint256 = 0 + # Only account for fees if we are not the first to deposit base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) for i in range(N_COINS_128): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] + ideal_balance = D1 * old_balances[i] / D0 + new_balance = new_balances[i] + if ideal_balance > new_balance: difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance - # base_fee * difference / FEE_DENOMINATOR - fees.append(unsafe_div(base_fee * difference, FEE_DENOMINATOR)) + # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR + xs = old_balances[i] + new_balance + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) + fees.append( + unsafe_div( + _dynamic_fee_i * difference, + FEE_DENOMINATOR + ) + ) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) @@ -874,21 +893,30 @@ def remove_liquidity_imbalance( self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + ys: uint256 = (D0 + D1) / N_COINS + + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + dynamic_fee: uint256 = 0 + xs: uint256 = 0 + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 for i in range(N_COINS_128): - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] + ideal_balance = D1 * old_balances[i] / D0 + new_balance = new_balances[i] + if ideal_balance > new_balance: difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance # base_fee * difference / FEE_DENOMINATOR - fees[i] = unsafe_div(base_fee * difference, FEE_DENOMINATOR) + xs = new_balance + old_balances[i] + dynamic_fee = self._dynamic_fee(xs, ys, base_fee) + fees[i] = unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) @@ -931,11 +959,13 @@ def remove_liquidity( amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() + value: uint256 = 0 + for i in range(N_COINS_128): - value: uint256 = balances[i] * _burn_amount / total_supply + value = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts[i] = value + amounts.append(value) self._transfer_out(i, value, _receiver) self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds @@ -960,6 +990,21 @@ def withdraw_admin_fees(): # ------------------------ AMM Internal Functions ---------------------------- +@view +@internal +def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: + + _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier + if _offpeg_fee_multiplier <= FEE_DENOMINATOR: + return _fee + + xps2: uint256 = (xpi + xpj) ** 2 + return ( + (_offpeg_fee_multiplier * _fee) / + ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + ) + + @internal def __exchange( dx: uint256, @@ -975,7 +1020,7 @@ def __exchange( y: uint256 = math.get_y(i, j, x, _xp, amp, D, N_COINS) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR # Convert all to real units dy = (dy - dy_fee) * PRECISION / rates[j] @@ -1243,17 +1288,13 @@ def get_D_mem( @view @internal -def _calc_withdraw_one_coin( - _burn_amount: uint256, - i: int128 -) -> ( - uint256, - uint256, - DynArray[uint256, MAX_COINS] -): - # First, need to calculate +def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint256, DynArray[uint256, MAX_COINS]): + + # First, need to: # * Get current D # * Solve Eqn against y_i for D - _token_amount + + # get pool state amp: uint256 = self._A() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) @@ -1263,25 +1304,35 @@ def _calc_withdraw_one_coin( D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = math.get_y_D(amp, i, xp, D1, N_COINS) + xp_reduced: DynArray[uint256, MAX_COINS] = xp + ys: uint256 = (D0 + D1) / (2 * N_COINS) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + dx_expected: uint256 = 0 + xp_j: uint256 = 0 + xavg: uint256 = 0 + dynamic_fee: uint256 = 0 for j in range(N_COINS_128): - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] + dx_expected = 0 + xp_j = xp[j] if j == i: dx_expected = xp_j * D1 / D0 - new_y + xavg = (xp[j] + new_y) / 2 else: dx_expected = xp_j - xp_j * D1 / D0 + xavg = xp[j] - # xp_j - base_fee * dx_expected / FEE_DENOMINATOR - xp_reduced[j] = xp_j - unsafe_div(base_fee * dx_expected, FEE_DENOMINATOR) + # xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR + dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) + xp_reduced[j] = xp_j - unsafe_div(dynamic_fee * dx_expected, FEE_DENOMINATOR) dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + # calculate state price xp[i] = new_y last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) if new_y > 0: @@ -1687,6 +1738,22 @@ def stored_rates(i: uint256) -> uint256: return self._stored_rates()[i] +@view +@external +def dynamic_fee(i: int128, j: int128) -> uint256: + """ + @notice Return the fee for swapping between `i` and `j` + @param i Index value for the coin to send + @param j Index value of the coin to recieve + @return Swap fee expressed as an integer with 1e10 precision + """ + balances: DynArray[uint256, MAX_COINS] = self._balances() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(balances, rates) + + return self._dynamic_fee(xp[i], xp[j], self.fee) # TODO: Check if xp is correct here + + # --------------------------- AMM Admin Functions ---------------------------- @@ -1728,13 +1795,19 @@ def stop_ramp_A(): @external -def apply_new_fee(_new_fee: uint256): +def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): assert msg.sender == factory.admin() + + # apply new fee: assert _new_fee <= MAX_FEE self.fee = _new_fee - log ApplyNewFee(_new_fee) + # apply new offpeg_fee_multiplier: + assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum + self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier + + log ApplyNewFee(_new_fee, _new_offpeg_fee_multiplier) @external diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 4d35e2a2..4e0b5d55 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -57,6 +57,7 @@ def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: amp: uint256 = StableSwapNG(pool).A() * A_PRECISION D: uint256 = self.get_D(xp, amp, N_COINS) + # TODO: Add Dynamic fee y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwapNG(pool).fee()) x: uint256 = self.get_y(j, i, y, xp, amp, D, N_COINS) return (x - xp[i]) * PRECISION / rates[i] @@ -86,7 +87,10 @@ def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: x: uint256 = xp[i] + (dx * rates[i] / PRECISION) y: uint256 = self.get_y(i, j, x, xp, amp, D, N_COINS) dy: uint256 = xp[j] - y - 1 + + # TODO: Add Dynamic fee! fee: uint256 = StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR + return (dy - fee) * PRECISION / rates[j] @@ -163,6 +167,8 @@ def get_dy_underlying( D: uint256 = self.get_D(xp, amp, N_COINS) y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D, N_COINS) dy: uint256 = xp[meta_j] - y - 1 + + # TODO: Change static fee for dynamic fee dy = (dy - StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR) # If output is going via the metapool From 0437ccd4d4c48cc9c0a14a8c8a6da51be95a6f56 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:29:51 +0200 Subject: [PATCH 121/337] dynamic fee view method in views --- contracts/main/CurveStableSwapMetaNG.vy | 7 ++--- contracts/main/CurveStableSwapNGViews.vy | 36 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e3a72869..5a245eda 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -56,6 +56,7 @@ interface StableSwapViews: def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dx_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dy_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view def calc_token_amount( _amounts: DynArray[uint256, MAX_COINS], _is_deposit: bool, @@ -1747,11 +1748,7 @@ def dynamic_fee(i: int128, j: int128) -> uint256: @param j Index value of the coin to recieve @return Swap fee expressed as an integer with 1e10 precision """ - balances: DynArray[uint256, MAX_COINS] = self._balances() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(balances, rates) - - return self._dynamic_fee(xp[i], xp[j], self.fee) # TODO: Check if xp is correct here + return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) # --------------------------- AMM Admin Functions ---------------------------- diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 4e0b5d55..4243f094 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -19,6 +19,7 @@ interface StableSwapNG: def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def totalSupply() -> uint256: view def calc_token_amount(amounts: DynArray[uint256, MAX_COINS], deposit: bool) -> uint256: view + def offpeg_fee_multiplier() -> uint256: view interface StableSwap2: def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view @@ -306,9 +307,44 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u return dy +@view +@external +def dynamic_fee(i: int128, j: int128, pool:address) -> uint256: + """ + @notice Return the fee for swapping between `i` and `j` + @param i Index value for the coin to send + @param j Index value of the coin to recieve + @return Swap fee expressed as an integer with 1e10 precision + """ + N_COINS: uint256 = StableSwapNG(pool).N_COINS() + fee: uint256 = StableSwapNG(pool).fee() + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + return self._dynamic_fee(xp[i], xp[j], fee, fee_multiplier) + + # ----------------------------- Utility Methods ------------------------------ +@view +@internal +def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uint256) -> uint256: + + if _fee_multiplier <= FEE_DENOMINATOR: + return _fee + + xps2: uint256 = (xpi + xpj) ** 2 + return ( + (_fee_multiplier * _fee) / + ((_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + ) + + @internal @view def _base_calc_token_amounts_deposit( From 23c315d24d34930a9a8abfe3713f047ac582a990 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:53:48 +0200 Subject: [PATCH 122/337] add dynamic fee to view methods (except in get_dx*) --- contracts/main/CurveStableSwapNGViews.vy | 36 +++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 4243f094..3fef29b3 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -89,8 +89,9 @@ def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: y: uint256 = self.get_y(i, j, x, xp, amp, D, N_COINS) dy: uint256 = xp[j] - y - 1 - # TODO: Add Dynamic fee! - fee: uint256 = StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR + base_fee: uint256 = StableSwapNG(pool).fee() + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + fee: uint256 = self._dynamic_fee((xp[i] + x) / 2, (xp[j] + y) / 2, base_fee, fee_multiplier) * dy / FEE_DENOMINATOR return (dy - fee) * PRECISION / rates[j] @@ -104,6 +105,7 @@ def get_dx_underlying( pool: address, ) -> uint256: # TODO: Add get_dx_underlying + # TODO: Add dynamic fee return 0 @@ -169,8 +171,11 @@ def get_dy_underlying( y: uint256 = self.get_y(meta_i, meta_j, x, xp, amp, D, N_COINS) dy: uint256 = xp[meta_j] - y - 1 - # TODO: Change static fee for dynamic fee - dy = (dy - StableSwapNG(pool).fee() * dy / FEE_DENOMINATOR) + # calculate output after subtracting dynamic fee + base_fee: uint256 = StableSwapNG(pool).fee() + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + dynamic_fee: uint256 = self._dynamic_fee((xp[i] + x) / 2, (xp[j] + y) / 2, base_fee, fee_multiplier) + dy = (dy - dynamic_fee * dy / FEE_DENOMINATOR) # If output is going via the metapool if j == 0: @@ -233,6 +238,11 @@ def calc_token_amount( # Only account for fees if we are not the first to deposit base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + _dynamic_fee_i: uint256 = 0 + xs: uint256 = 0 + ys: uint256 = (D0 + D1) / N_COINS + for i in range(MAX_COINS): if i == N_COINS: break @@ -244,6 +254,9 @@ def calc_token_amount( difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance + + xs = old_balances[i] + new_balance + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) new_balances[i] -= base_fee * difference / FEE_DENOMINATOR for idx in range(MAX_COINS): @@ -283,9 +296,14 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u total_supply: uint256 = StableSwapNG(pool).totalSupply() D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = self.get_y_D(amp, i, xp, D1, N_COINS) + ys: uint256 = (D0 + D1) / (2 * N_COINS) base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + xp_reduced: DynArray[uint256, MAX_COINS] = xp + xp_j: uint256 = 0 + xavg: uint256 = 0 + dynamic_fee: uint256 = 0 for j in range(MAX_COINS): @@ -293,12 +311,16 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u break dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] + xp_j = xp[j] if convert(j, int128) == i: dx_expected = xp_j * D1 / D0 - new_y + xavg = (xp[j] + new_y) / 2 else: dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + xavg = xp[j] + + dynamic_fee = self._dynamic_fee(xavg, ys, base_fee, fee_multiplier) + xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees From 45a5bf3baf34f6be56e12d4c147b3644aa520b8b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 25 Jul 2023 17:13:53 +0200 Subject: [PATCH 123/337] add dynamic fee to non meta implementation --- contracts/main/CurveStableSwapMetaNG.vy | 1 + contracts/main/CurveStableSwapNG.vy | 108 ++++++++++++++++++++---- 2 files changed, 91 insertions(+), 18 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 5a245eda..0748dd84 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -783,6 +783,7 @@ def add_liquidity( # Only account for fees if we are not the first to deposit base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(N_COINS_128): ideal_balance = D1 * old_balances[i] / D0 diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index ee91f8e6..aa0bada4 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -56,6 +56,7 @@ interface ERC1271: interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view def calc_token_amount( _amounts: DynArray[uint256, MAX_COINS], _is_deposit: bool, @@ -127,6 +128,7 @@ event StopRampA: event ApplyNewFee: fee: uint256 + offpeg_fee_multiplier: uint256 MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory @@ -141,10 +143,14 @@ PRECISION: constant(uint256) = 10 ** 18 factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] -fee: public(uint256) # fee * 1e10 asset_types: immutable(DynArray[uint8, MAX_COINS]) +# Fee specific vars FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +fee: public(uint256) # fee * 1e10 +offpeg_fee_multiplier: public(uint256) # * 1e10 +admin_fee: public(constant(uint256)) = 5000000000 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 # ---------------------- Pool Amplification Parameters ----------------------- @@ -159,8 +165,6 @@ future_A_time: public(uint256) # ---------------------------- Admin Variables ------------------------------- -admin_fee: public(constant(uint256)) = 5000000000 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 MIN_RAMP_TIME: constant(uint256) = 86400 admin_balances: public(DynArray[uint256, MAX_COINS]) @@ -210,6 +214,7 @@ def __init__( _symbol: String[10], _A: uint256, _fee: uint256, + _offpeg_fee_multiplier: uint256, _ma_exp_time: uint256, _coins: DynArray[address, MAX_COINS], _rate_multipliers: DynArray[uint256, MAX_COINS], @@ -626,22 +631,35 @@ def add_liquidity( if total_supply > 0: + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 + + ys: uint256 = (D0 + D1) / N_COINS + xs: uint256 = 0 + _dynamic_fee_i: uint256 = 0 + # Only account for fees if we are not the first to deposit base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + for i in range(MAX_COINS_128): if i == N_COINS_128: break - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] + ideal_balance = D1 * old_balances[i] / D0 + difference = 0 + new_balance = new_balances[i] + if ideal_balance > new_balance: difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance - fees.append(base_fee * difference / FEE_DENOMINATOR) + # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR + xs = old_balances[i] + new_balance + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) + fees.append(_dynamic_fee_i * difference / FEE_DENOMINATOR) self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] @@ -732,22 +750,32 @@ def remove_liquidity_imbalance( self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + ys: uint256 = (D0 + D1) / N_COINS + + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + dynamic_fee: uint256 = 0 + xs: uint256 = 0 + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 for i in range(MAX_COINS_128): if i == N_COINS_128: break - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] + ideal_balance = D1 * old_balances[i] / D0 + difference = 0 + new_balance = new_balances[i] + if ideal_balance > new_balance: difference = ideal_balance - new_balance else: difference = new_balance - ideal_balance + xs = new_balance + old_balances[i] + dynamic_fee = self._dynamic_fee(xs, ys, base_fee) fees[i] = base_fee * difference / FEE_DENOMINATOR self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] @@ -822,6 +850,21 @@ def withdraw_admin_fees(): # ------------------------ AMM Internal Functions ---------------------------- +@view +@internal +def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: + + _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier + if _offpeg_fee_multiplier <= FEE_DENOMINATOR: + return _fee + + xps2: uint256 = (xpi + xpj) ** 2 + return ( + (_offpeg_fee_multiplier * _fee) / + ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + ) + + @internal def __exchange( dx: uint256, @@ -837,7 +880,7 @@ def __exchange( y: uint256 = self.get_y(i, j, x, _xp, amp, D) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self.fee / FEE_DENOMINATOR + dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR # Convert all to real units dy = (dy - dy_fee) * PRECISION / rates[j] @@ -1184,20 +1227,31 @@ def _calc_withdraw_one_coin( new_y: uint256 = self.get_y_D(amp, i, xp, D1) base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - xp_reduced: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + ys: uint256 = (D0 + D1) / (2 * N_COINS) + xp_reduced: DynArray[uint256, MAX_COINS] = xp + + dx_expected: uint256 = 0 + xp_j: uint256 = 0 + xavg: uint256 = 0 + dynamic_fee: uint256 = 0 for j in range(MAX_COINS_128): if j == N_COINS_128: break - dx_expected: uint256 = 0 - xp_j: uint256 = xp[j] + dx_expected = 0 + xp_j = xp[j] + if j == i: dx_expected = xp_j * D1 / D0 - new_y + xavg = (xp[j] + new_y) / 2 else: dx_expected = xp_j - xp_j * D1 / D0 - xp_reduced[j] = xp_j - base_fee * dx_expected / FEE_DENOMINATOR + xavg = xp[j] + + dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) + xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees @@ -1690,6 +1744,18 @@ def stored_rates(i: uint256) -> uint256: return self._stored_rates()[i] +@view +@external +def dynamic_fee(i: int128, j: int128) -> uint256: + """ + @notice Return the fee for swapping between `i` and `j` + @param i Index value for the coin to send + @param j Index value of the coin to recieve + @return Swap fee expressed as an integer with 1e10 precision + """ + return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) + + # --------------------------- AMM Admin Functions ---------------------------- @@ -1731,13 +1797,19 @@ def stop_ramp_A(): @external -def apply_new_fee(_new_fee: uint256): +def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): assert msg.sender == factory.admin() + + # apply new fee: assert _new_fee <= MAX_FEE self.fee = _new_fee - log ApplyNewFee(_new_fee) + # apply new offpeg_fee_multiplier: + assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum + self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier + + log ApplyNewFee(_new_fee, _new_offpeg_fee_multiplier) @external From bb996a0e5b146d3dd48381f136cd97dcd5da69e7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 25 Jul 2023 17:30:47 +0200 Subject: [PATCH 124/337] wen disclaimer --- README.MD | 4 ++++ you_shall_not_wen.jpeg | Bin 0 -> 52234 bytes 2 files changed, 4 insertions(+) create mode 100644 you_shall_not_wen.jpeg diff --git a/README.MD b/README.MD index 2da08b64..8a6e8cd2 100644 --- a/README.MD +++ b/README.MD @@ -2,6 +2,10 @@ Permissionless deployment of Curve metapools. +# Wen? + +![STOP THE WEN!](./you_shall_not_wen.jpeg) + ## Overview The metapool factory has several core components: diff --git a/you_shall_not_wen.jpeg b/you_shall_not_wen.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..376e1fea79a908d13685a2eee4c38d05a9e78616 GIT binary patch literal 52234 zcmeFZbx>SE^C-HoxVr^g*u^2Z1b27WpuycW$f7|P3j}u$t_i_{26uN2EE)(TA#d}2 z_3C$Dy}GaJ{eSz^Inz7cGt<*Gr$@Ta{$2UI10YaQP*wmSAprnLhzsy{4{1eNR`#`) zj;4aLn*4tWqW}b;a039YZa!W*iZb*@#wPUWJO3^5k7i}#?eQ=D-vFZC`=x)i0|4`! z{~MnFuWU?PJ8v5V!xiENdm$P}2up&%N$mfNv;4!Y|BH+L!~K0cd=NZZ|8Oro9a#i! zi@;gz{}j%WaYV(owOz(oK6J`@0GpZ=e`|Ivwum6z3jszX6Mk>9)l z0B$M)09<1LfOrM~z%u<04e|Kj$VQJKQ6uDXM_l#*7l0jr9-s_x1K0q#5Ew6j8^8k) z{JRd21ptx%!M`UE0VrrF{{RCG4HXR&0}Bfi0}~Sq2OkFu8xI>36PFMd51)X5kN^vZ zh?t0g7=aV~s|4v^NgxUaq96e_CN?7X|0n$I0}x^$-2wxENDKgELL?v|(%&HfEkfTY zK%{>o@&5)SWFQ&}Dmn%x7J_a?0QlE3G7uF76%_*q2Z)ITKn9|q5~2}-(0PcZwJ}H- ztUNJ!BhpA2Wps*#b(`8|$tb{lUVg3p{IW*YKEAN5`Q58)a)^MCo{hJEWOPZhh(V^D zyuP7bRC>z-qKOxX#{Wt1{}cfQ6%8FhAf-QbsF2u#BvZXT;w%01ko+K_&!90v?T^AOHwM3PXkfnE(p-KR^DD4*p-`AUgd? zu;(v8KK(|z!w~PTIZTBUNQup2%0BI+ED3&L7aYLfjNi`*B*kXD3w3`^{0kVPEB*fV z&i+|NIypKGPFnaR`WFB$IMn|=_yvrDq5CV-@RsX$8eISNx1Jkq2Lh1he^JFQ{kOcA zV?y*&(UJ(1`*Mu-zh%w|KRN?xHe~PzZsZ!A*B{ej={s-wBXK!=-55l9d z+`X%x?Y3dBy=!iww_(UAtbfL9A7jA}uRaQ!KhlF_K5Vl6<4pN48!`#&=c&ra>@cMV zmBJ(QM;48KEHYUKXWE^m`A;wpVdIOr-1wQ?m^CLHS79#PT_phwV|}unVQKmoK6XGJjIRqZs#>LqND3|`Z#5KqCV$U=frumXvx;GU`QW9A z_WEe^k=fFkqyS}mvgbyF#8-pTRdjlWrBNnYvUAD*!BG-iaEwqALRsA?EV{w@hPU_# zHT}2aQqfp%y=tDVMjzNd9#LmSAd?Kk%KQ->6`w18tPE4OL@2e+Eje9QBBNoR&Z zCPP;vSV7D2h?-*h;6ob!DiKNVh2*UrsPeskV)-hjF%?3PFP%C8ywAHjwFmii*2KSA zC4I`TY;PRdII$d(Ft@0~cN)vD%Ki*wMGNv^Nt5)dv7&hOB*osSGN#4p1Q3OtX`*Rv zs*nDsETyDR=|K8}ipdM3A?5)0KZB2u{}hd@rHE#Z;^J#8<57~dEHCkEiqp6{K#&;+ z(;x5;vf`+q`!7cP=*F0qgGgFM2r;)_`~~{!x=v7T=H4noZ*%UIC08Ejn;6>pq6qu!@o4cN=; zRw&0Bqtj?>EyK9Gu!+Bj0au;ETTA`|Y#pE4LyfABzaj=a*_B<0tDPq{SNo!8QP}vn zGJ}6<44L3a%Bm!6mj6+~jaMU=3c52w2mWAwqL<1*bm0##9DDgs3{}-;ou(=cj(|U~ zbmuQV#WWbuPKmfR6(oXe^jQjtI20N4%9-*tJrkf-erp*nM16%2Q^_BmVfjRY4C2Bs zA)vwV@Ip2_9tFz`C!|?&AWQV!(G2<}hHDFF^uT|sZy`!;mB zY5K7g3*$1WqJ?f`U1Wx%Rs|*jw#*e@;2Et0O*slWAePRqM-=-5BKmt3DV;sB*nXM;c@ESpfM2EdgW2;~kx&v;0c z1`(Gy#CoEO7~jFSfST;DBbyyRhdDKyVe!$?;?rbkP(QGR5N(MKMDIjJHZ{q5Btm4` zB6x0D*U_uh=nvJKM%PhZI$#O#7bTyOnv#GV{Jr$-qWpM+tFgo3?=>`ZRt2oZBTDHr zUrbO5i6V)QAep~Hw{LZFcd*Dh{qU!k>X-hy@hXdRHM85v8S7y~G)&?8Xjx#0@WdeX z;T31Fwv>Pt@R_@^*&%<>0_zXT_UCuv@pV}>6Y$a#xA3-g2dhwZb31iW)}qSzQm)_w zE?gU_F>ortEOw3jy#C&KaZ_wTAe3PiUFo~hDN}8mp7zF>I>#bM;`^N}N{gSX#2Y~6 zqp_A7L&lB*x9B}PNVmR=o~teN}>?wntK z)zI6JC=s}^_7sDB{2=UXf?fm&?DKf-{-++%u^boLZ}}PvRH7v&Cewi}r#0*O zG`=(x$qwMk#L2D(bjG+*iK4WXT1mM-G2-vPTnQ9uG@NLpc5W-YZ+lL!E7+QcsK4$W z*;l$fI+pa590gu}j|^iX-y}2bnjAz+JmTL?&|p(QeXTE7L344c%-d*osR0di+Gw z`ieBms#>zHRgo11#`M{?3zKfgiX85a&K=xcd2Rh-uTQqk=>Tg;4yujpLi+q1`Rr%y zEvL}Ml-lRRmy7p)dSi4kXEC}{Ki9c7^4y(eo6YJ=lWsqQ^4W=OfBQuB-CS;@CR6VL{>nf{Cr4yib zOQ~{gk{R-qXhQ-N}$;p@;>dY~XKV zo?=oAm;yb-k-3SAIU@w2c!(qwCZ(Yh#;TP9la-{88Yf6fgE(xQ2BqQl;p{VJuDS3< z5QEX-$>eAq6Yxdt$&*0y59-V9P7WI*_Fh+?O?cP4Xdg<)*W;I3Cx@o_fE%LEY$FY> zlx-u%8UrSKRB*BESB(p=US$dQ07zGg1G-N0l%u9&!sJ7`z0%L`Pnqr0Uww9T!Aa1X z()^m|9X*b5jQ!cSqQKyl(#H062!6>f5uXbb;L-bKu35#1$UXfiROf}435Bq7D>IlG z2nibN7HiEOb;TH&WV(MU4Gz`5Q7Pl%O>HHFy1Um1bu1E>h}UUi7Q`q2#6!13uvCbY zk9Y?|Qs;hB03?+Cpt~<3&a-V`tLCLZDG_O56{wun!2YPLvPy78m+{-kXn)I4T`#SS zyy_+yI}914(jpLs9xvDm91YrGF@A^)q~|}>?*`KDX|g6G%#Bjmss0v@{R3;fnTCoy zo@u;OgaBghs$@dgTyv-blo8})L=X&So+%sq@t@VWjK_!cGbl1lMeaWfbdZEH(g3~> zPGGUEuonRywS+y!6D#Z5^7zbdT52^{UC8XxD|0uP2xGMeQW0fdm=uS4pfAsAjRZZo zCY@6JI@5Y?_QRW;sw(Pifky7fgTH{Su1jruZzsnSvmH6tjj(c$=}lNN^b7*Y>1Un< z1)@{4;)!M8F?$|Q`V9?O9NWBm*Hb2*?iH$?e%8t^1H!}lroHJZ&f-SfO!IL22;cVxc(hxB{(x&)`B623@j zisTLQ5$%dzn?UdQXOohWimZ~7Qblv?1eW&}kF;m@;Y+Pv%FF=Ng{sm}0ayWL%(A@7 zaAy!ff9Bkqz@GDO&$6Gn{{o^mzSNy%Xa4E@lXw;+-QfFj4HaDv)wwRHVxr2lLO$Il zo^&A%4eMioSX8Y20OhbS{ImA`o425?_iy{*ua|sP`@cA3_1+(RadxY|+Y2V^tT`zo zO;cNLQN*8}EDM(xPF)uK;2*PU+}Syi5{t4AMPDrFaGb4wf_!YvFb?=~YFgj*L9~z- zrZg;h$c`IoPL?vKFK{c;G-u2?opW&Yf|`fuEslWR=lnKvjo$mWr@hUcT`>LE{P{KQ zAKf{x56Y6}QXcL44MST#&NzOt-D=o4W4@BT^PNjd*-TW{*7!*ob@rNF>M;Wqc)s~P zEcZKp^Hf^L%oanzUqC5~b)&^eK9hZA2z@gpH9n+yXh}>mRpb8Hn;H$@?8*DWd^Qwf z?sw0-AgJ<7PatzSA)nH}ZA3uvoxU!ox@uJo@7(VRz76{Hv}`o7--HdK=eqYnKHao= zKF4d!ioV`8K{ke4E_xkUIp+^1EIZd=XLAlFR#sYYY_Nw-da%Z9-fl4C>h~Idu7(q; zdfC8sEVl1&ia(t`MoHtopIKH{Ta5`OZx`(9^Po0EM?$)_S!G_Cm;LqDsO_N@|8*Nz zQ|ouCd{9mNkAl)|5pjr?%BQ209~w`EiZ`eim)_b@RPTxrS>!wM!rEy4{-R9hH4a z45?P`gihT(|FG)2msMB$6r5fKOz{a!O@EEt=6FfA{kB`S>XL4=@Kh82Zu?A9^*Qre z{9!vpG2r=nFRbP3wb14EwcAwS_{7UI3!3*13f$3CPKfoKz*^bOC3V0Q>6Mofjv=GA3yAHOqdhS&L#phStL&DqIY9&XPe8a3+QV0UETOGKmIBgi#i~tdr)~I zo!#k;o6$}sR@QmLwU?L?M>B(`!v~M1l@SUVC(0)VDGVqdR1)U*f&x`B{g7k(0>r-%(M;&pj6}a zP#_{dD9}{~c**o6lxPEcU^$;2JAM`)ejTA4kQ&hRN>e>(!`=&lkssJ#X$>duFnum{ z91JupBVte&pT7#lvO;H$hEx$iSwL|bL~f7FOli}0APlJG69}_N1xY+ojZU5sX_&ia z8#(5u9iA0>^SHCZY}!E%7q=*zd&5_X2AZ)O<<@=SUG-MN(SypS95;N7g9)(By_-R3 zLEfY>0bB_G)Tmi;3dz76TgIs&6}P-^barG>N7r|?B;i%Gfdj|V899D}+0<^~;2+f_ zr7>5^gN7^UMX(Oz8MvuuJ8Jy}1OWbQ?`3br!v6wD6K*59qfz8TbBHv+ZW}h&+AL;! zh>@NVHO@c?#M)O`|t%uE|!nhyO4kJzAh z>YrRcs{0ICT$;-NkuLRv>_O8|+jVZOU^%ERAuY-0GBUS8q|C)?%2{>7ND8&jo|3Wq zqem!K(#4o0KAk*Iuz5^p={f5!$Isd5VTC_T{{rUfxBmh*sz00mdh=xNd3pg~p6xC8 z{>G{1Oxe+a?PJD*&s{4_yu8GFSCg;6IKTgclqY})o&As}sq-&@OcETC@mq%PRSW%n5$WWlKfUDO%&~XPLTBjweo~H3h5gC9oWLR)b^Ah_b;5VY@+8Rkb$$7smAFe(jEU7lM}jD0N9DbGboIo4C4fmxL72 zbAx?JD@>Sjl)NLdOtKnzqF3;|QIcOYQ@{MZrd0(3BJ&>46hS&ah@abxFRzrk;_ zuly`te1^N;K&5H4bf)fE@6^VwP*_r)o0Xe+myvNbAFHlQeEY(&9UppFTnSE3LH2bv zTn~y}q6u6O zp{(C3734&nlyie9l@!G0VYG@^r;P&Wi2Z&%zMTg*4pFF9FkOjWY||4fPP{N5qlmf= ze^O~`jOTGvgWGIgB_F&j5XD2MF2rjM zpweDNcEy6)g1rFD6PN^0WH2M<7*B>8u8fYz4#{697&K{#^(3<_mbA(j?&9;Bs4SkSa%DMIBhc7p zz5=aa_DYrU-$`mb=>@uTc~%N{M)6JiUFzN*DrpCamQ`a@D)sA)0wCcVX)seQTn;Qu zU6fen?sKpak2GLDL=BBIZd6)MACt}R;wigVsnxR(#}ZAc$&@KrCJJPg5AX24F8E&M zQ?NE2AmAN3>dh=k-<7Avt1=@tMQ%IC-M(CQP)+|cosdG3!>jSzskTEc`_uD|b;~r! z|EoSoGJ-wh62%7VbRp;{_r$?uzg_URteBn72Ec7cM7JW!EMrvR@{8w?pO zOev7C)S>XOUQ-{wM{&0SQL`Y=*Oh@W7yGD8Mr#{9&)+)aiFLLYxhx;k@G;8{L4F}~ z#3%YMz|O<)E#-5E_3?M!9bv4$;%;>!KsJCecuCqM=M6mfky=B@<9%5!Fa>-8SDBd; z(JmO3X0)Y06duLUvMs+EN1F0Gh({XN1D3*d=NaL;GlX3|3SRGzvZ3b<xPg3Icvv^ZU zQ%qwQtvN^d-kV`Mt$STc~!7)xU^I==v7>%u8K z3QpGaU2_7m*87lc7T4PdeR{^uJjq30GA~Xl72QC;HpezJ+fSfcmBziYzA{cv%;Qp9 zW=gW2lC7={y}#CG1t%Tg7qJ^WAkU%cj6W!Yf_n<|?3=%j&{UIVGis&gL1>N5(PVQ6 z;48c0i6X=B(G+)KO={}8L#BKrzf2B$AZCHF3N=+r55F>KIuM9OpxVU>5Ad?2kdekL z9_qmy4=1)gCr-CLCrzb*f|Lb8!qW86WOy0|3SwFk^HbBLNyVB&_vLWq?Quqj$jzot z+Z9SHYqNi1iOPGpnwf>wtlHLPl}w1UGhR1}X=mMT+OP5dD$6AwUnXW8G+Dywe8#%2 zcrveQv)fv9t}OSi2%)#6>zTd?dTHP@y*f2rW@R@F-wx^QKrP?jv3h*#G{R)^%5$$} zI>@lZr1>v^y#`!v*Ph}&$R8g27a((H;$QOQt*PBL7rQuxCZ@hYx&b%%c}fpC#|LJ0 zxFJ_;(dc=ZP?E?_P*@n08t;p9N~9@gnLI8eycgq^$U!~H~#Zj>SFH0vFSh9>8jTc<&0c6KyGP8kxeke>(m8P0AmLAiWf#$XC%C}Uhz}f z;hfSWGq&XbVaUVXx`Plt=QYJ7>}Sx~{6J<4e^ZKhpE4)o87K|mKy;yC-)~a2JuS4! zZQd`{Bp@-Ysxh0K{YE_?7UwZ?*PvR%lGOcZsB*L;C>wFo{l`Z~ZZvnd5a9pBMyFpj z+CAuJBW35p;W?5XiyDevdGI`0IrY7%a|D*KKWre{Y+`j>(+~{_+)Fz%+%9O#6SQ=u`9DJw+WxpEqqW( zl12HjmV1M5oePyB%meY~RRc8sdj`pZI;=aWfi!|ye_*GK_ee{bQW6;?L=`sjq+>SC zVk6s#Zf(}1sM>8Kuyu*^Dkx%+H?48%$d)^>VEkQ~>v{fpnGH1!ho#5`C8LDw(dYHK z3GtT7S1QR3Dt_%neH-ZhC=bU!8(ba*^MyS{n)^$-Ju9CAl~$>idiO;Y6Zf};so$>8 z8l6FGKf35`jKZbNR@aiX1>~37t1k18-)Pd?xt|;UyjXf{L$z(-C2nSt>=pO;w6O-w zY?&J@%Q$6Zdf1)G*7SVOuMp^*aGdQt37IOtZi{*oV4SU*_>|DBl3NraLX(FSoy^e+ z_hWQKKk7BqZP;eY`9Y;l1QkDOx*s0joQ;}hXIq!%U{ECR=Lg_77;MEvG|^#h?CiQw zD!zD~^!eF2mrwo5M-`Wi-M96o3tFI=TWbcY#bc0+D%kZIUlUX$lMQ^VN44t|nc{M% zFtM9dNn9%QW^MKDz*2O{pUMSJv}}ZuJW#2qMhn-(Pr}Ike_*>l3554}E8t^WxX#Xu zKj;W21a^klUd^A8vADC}H&y@Hy{#PvS zV|4jPycmWT6RKV2TB8T)iX7MSo(wI%#SibzQIfVcsO`tR66%E=zdN5CCU#R59Rq)T zJjQP8X8K0Er@%8%e5Uky?c)$5a51QxMm4eOT3{_;>SUKR1749f+i4jUNk#~-H>uc? zA2x|Ie(FftPH1>VkG7`}O1@+@T8S;;3=Z)sCB4`gizL7vY#lnddF4r*7vWQd+TDrWv#2UksR>nqbH>w43jx%~>kcluAureCy^jv7m zk;t%;ww(L{c*rr*DPJw{!iPZEs)+CvrGFFNe-{bzt@HYc<=u)P4_dQ&Xkeeg3dm&pyc|-3n7n>NN7OYwKdb|wot6o0+h`rJ2 z#{Nv($WnVl6t7D>dlXWwGx7R#$8N`W7(RHMSO>q``Y(S$!<&B0R94Mjl&lC{U%w@p zJzUKcw5u8H<+!bO!vtO{?WW3%cPd!cgaiy@4ZpUhbW<*56+Tao$L_~HloPE!I!Ato zWX?zF$0j4Ck{evzjPl=A&cEKU2bW0+O9`hE) z6u#}n`JY3iUH&h$szOvK!_oy~4Pn6^xp)dXjc(Jh1f=vcogT_vuIXGWVL2pBp%7Ss zULz$YCWXRwt~_Q0-@lEO^rjMRo+wi}I;{#Nt5MegFR}7C3AqY%RKwl2e^dN(#&41o z>tYNo2UOdc0lWO98?Ipk9$}RFt*jDX*Qs2BEq};p{TDKUyJnY%;PSArR^yYZbaJBS-+)jkH6& zDqeSBSBjY=EIg23NN}eG;P(Ug-I+SOcE$MFHEiqwc5T+HbDv^zJd8v@zFSFm@E>ox zl;2jDUe%vNntn!I_Ry*{XL702caXzleau1=KgY#sp6G3ar=B@mOyFK$Xy+uciC*#@#@7T z@do=Eu3i*usc`ktt8KB(m*}TypR*D7Qp9S=RtKN9Pbh5^RmP*jeM}h|fLmrww~%H( z+O#Nr2Y1X=B`8n4o`{Ti;L-KsX1dHHd7T4|X=umqdb`;eoVaHYscDI9%d>!6o*q!V zvQu5dm?wl<{|>wMg^%}m4=8&mrz?FIm`7UODTZt!E|w)P2gh5Pdo=$|*4?hsA&Si1 zrH?*h-Ytnu#4*q2} zzngwIKI>HV=jbJsV4mdz)(@}Nrf-nC4a{k5*?rq9e>$H|^I<_3Qp*9IAlA7X;p>ar@7=CFD3y-#}6RSQ=FsPbsPv*Nvj z@vPFv*E{+lZ<%qr*!U~VMtZbmG6*}GlS%M;Vg2AaP`kBl?-m#x#nd(5bkQyrn}LG< zC%g^~2~I-Pn(eu&QR?}#H!dF;_x-KH?1A10`WO>(w+=1c>-OBMf*SEUK@^(vSOFHf(FcwR z!$0Iz93bX{$SKE;egh>@<{>F&h#bv`?fw0D=}D_{JO!MDxnN8O2`Yck8;&PRUm#kp zu6;wb!yBpv?Fn=GO4O9B99f`1#P&PAEotnB1`W*hv7DOK_ACEa8G@qDp`-NV)9T6d zaI=h}r;Io$E@e?E$m>_m3g9{cx4!@fJpVo9QaN6)ZUb22`rT)9M@WTD+n4v)BHd=N zB){g59BoQ`QM!WX_G8|UBJNm=A0ZOA>MA3iBxWf&5>6Gz;hxZe&UVOb!t*J*&#A=C z8)8fbeFFes@>n0vy;2?;o(fSk?k$q7uH0iqGR3gjZ8(Vwt!$?A%)N1g z5SqRV(<|>D$;(*Fnl+UPu%ATkT|%USZdj_beQhXJ_LdaX-O zN{qQi?=f&GoxOxdX`OTKo|#UQ1iw$d9eJ&Xw(NhIhxC)(oU1r_-qm+@POMuqB8dy! zE&C@rFgNkSoHqFb@`s%*!DE^VlQE|}yN}KqRGu+hLE|<42ATo<&jQNANuOg3%!f_l zPtTms?7VTMfu8%4@@B60OrINdJ6i_?LzVU+0V20T6XNF-f^!PKoN~{P9GRXeRBFP< zosS%s!9LB%$KD0w)w+IYhN@huu?2zJbBh~xB9%6q1ruWn6TzkH^CQDPD#|g^$xJNK_-hDre?);VPq12I@HqpX$@%4;}Pc=)+V+!j`cKq`)5Kd>=z+D6PhwtjOKAE~v zjBo8dbhh1ZJ_TNuD4JUcAy5D+UigoH z{G!F{b(|L-=CpOeYL`qujZ~I))=FLvQJYccuKk8WX zK^vzM@7JIh^E9gdizL2jO6^=lwZXhR{7FBXqhy?0m#tX~V4rPMEPqE-Mu!qZRhf9W z2$CVE?xzX(C78gYeoku4VbBXt% z3W2CTzmf`==fvdD%&%=`YG|eklqW!O)cggKznLCw%+*qH{1L_K+M*bHnhruLJu;!edCtz=@_E(L*jNLrPRVaxbw^~$2ESV z$O+blCl>I2>{i&ZgG!l?ipRsNY{EN&48HW$+u&2O7aHRJTwN}n*e|rVZq5607;RfR zVLUpeZ!*EB8qN%BAW8i58h46(*okau_J`5on|DTQ>jJP`$O$XJviHhEm(&rC7R{8}~3T=VRC8fLZW<{Jle31#BcU=V(B^c(&<-N8R7mg9)ja-_lQw6njnkp7XJLG6{YoOj@%2N- zt;lrdrRSI@I1}f~T-(qYm=`@oQh*jYZF3&qw?4zO$)w!gqgZET3X4~#L=S}C^uz+@ z71tq{^U}l6%0xO*clSA@C${i}#wJ#zXCT26?Z<%KUPu6n^GwV>D*(Uk4S|A;ha)3M zX-2AFYm;2b2M6*6pW@JGE9WbpL!qdN`JSYW>0zP1V$*6_kG{*boG14i#q^lgpv)!y zb%&Gv@`^S^LPcbl ziZGkCEKbTKl~W6&B^{2$sNTOeI%-R#PG2=(buix_-U$$%GFUpr4OR1;mG0CX^<pecLPBQ4&bp%;QN4-NHtKA0-MgTC%KR&AdY{rQYeqh51O@^bcNr&^l-k1A)OnLf`e`86Rp&k6wC}9t%`y_S_>=S zhXmEyiD^DqV=L@#*i2e$&7(W9C{g|tFk?8iLz@X^Ct14H205EQ*3(R7+a4AXAwO`ddWrev`dFb-ha>YGmHnxQg07Q)bB;HK zC*tW;rFiAj?z;6(N4|x{i?^jF?phEFx?U@VuJ7IW!_>+Oe8G#KQ1UfL#kuhM=Yo;iWNY zB&HrdR69oI#!A{;7SGjT3-ek4gj5pgF~ zijmrn%+34@2xRC&j>rVd9b-Z0&m|I&c=2uSw3*QX8${vp2xqtSfKbFHgA68%_U09P z56OaMWlQpjML53=E71e1)Bu?h;8w!{Wv7T+^emr(6Ef0H!qa!peNU6|!Mjg08GU^7 zo0afo_{Sh%Ik~#^Wl%^YEa^Ryp-}oARtiWF&;PUh6Aa_u8Y}}EiPuBCWF!!=F;!5jhH_|}U(2wjcF#@t#gWEP5-YPQ3 zT2N_Pca!p=>H|1Y4m(XQy7?e67bvqK8t(1_W!($2KYJ3xiUeI;V8-i*AW>CR5zi54la$oxp1ooAz(bWob=~<1|&2 zshEVYI-XW#>V1ED7%A15mkardIS9y;$1dO91yegl#}V;aq}C1baCU-glz#A5hS&JB zZi>t1;mMAHgV^y($^*RfBWYlYwB?3Qj)yT$fo6kk(}2w7lm>huaNxoG!aN2_jLW(A zCy9`FYPQh51Se*=i`DffTJKe#+D#lytE6PgFbs)2e|?oRNm?OYNw*^aRj6TkdA`TA zq&~`G-g9?qJEJx+wi0uPok}NVSN6hVL%ml=meyz>fIpFR(v5#7hazC?wRGj^G&-0) z%OP)10nVE$MjhE~TZw~0`<-+)(y%1gp3|ro;Lkapl4ToM&A3hd9cB~>*xAr&-KGxV^d-{d_S*&)%pFPFnz&(^BGyq zJVaF1EP(nW@j_Eb>Dt}zOsba|8e9xYV|Z_?pvOd2v2jV#cRs6)xZ8v()hhV|xddBaED zAWD8A#~G)zvA}eqs@>y1MA~-oD1M^*BcQaR`}tMMCm? zQCU^BKGgPdLva+3^o8kSd+=SIO@s?QH`0nF*E=tq$X%Z>6K987aOc3NSFhTZ(+2- zUZo<;Y`VY)lG4;vf0Ya*OhunhAsI;3m;-@8%t~4ZW9~$y2eh^>@D4|mDqq-bpsx19 zSG8{f5jUduaZ}q*3%x(G-8hBCGfhi$S}0Q`MWsX}AzhU%uP9apv^eo8aTzBT?N(o- z%s2k(^o}1i?GRzRb!{Fo24|A@0ZpWB)kP?1V*jWlYc`)@G#J-2l_;I;76-Z{Pi(+> z#gWX9Caep#UCXwG-&x`X;Ozi{N&e83vpQDE?;XFeasIM$?r>1ZMOvIiW~K_RL3?H3 zhj)J2@A+oToF-tWQ}FIbi@sK~iExPUV0+&Pp83J+zW{gn;?HF3-TufBL#{ z)df9$*iU;OpKt3FBa}v*rM`|?e*vE9O#WmebcM9e6O)+*S=6El-BX&<4vbFi&M*Fv_xAtI)#Pp4csS)V6e&9u%4E) zS^YPB9}5?vU|HpD@{ON5I>%J$Bo?ZSD{RWn+1YpTQ|$4nY69Fe+m8C@!#lf}j|2I$ zpTZRx*>=_00eE3Vs3jy!pXjn};W(7;FM4mrh^At$j&;AI=?`L>)%u_P$41DD5 zt!k(&Zh=`K1+3NjVZIwQ!wb}h7wMU?<*%V<^Nz~g#>FXQmI%3yZ+#dw zH8QgWq{uJu>$~XtNO$3nu6>%vtNK`9H>Br?^yC4Ez}bDYGEw*3+#-^bF*Q;AG zG;B-sA34z1IOeMmLA760Ic|CBKj!2F{PvzYMXHBKCT9YsUi+rx(8RGOhY|Ce>t&(7 z&0O~Dkaym6*L<-!H>B3LN*2eWnOw=XcQ9o!E_BZ|X}Yie!j;5RTN_oUZA9(+8nS-@ z!~B7D6sL@}kt)iv{#QqZ@=awCncTcJ^yrs>lFq(yYskA~u`G6`DQuM&IYtX4ywWNV z$HOQ`wt|)Ng5n0r+*5b{2MSz{^WjS|k6J@qaRIX9`3~=pJh5y8T!m$q!v)8V1}wk2 z*v->bVkJ&Lg)rxw&C?Rw=w$byx5d7u88Rj4vL@Tf5~itDLctCNopSg?N^4=NC;VL< zBQ3`QMg`1jWg9FIW}ZlCql&oUjZu$o>L;&PucX1S*v}iTBX(My^EnpQxW7JKeHIIR zv2(k@GB3z1yw8~Yovil7m1wk0QM*oamdaX}$!t_p+0Fhz?J}7_OV_vaV6IM8Pi2`z z-Ax7uX7Tx~s>vF1FmpB$Pc&6pnSCCMLj*zQ&zk|usX#4s5P*1vu^FH)K9~2%^mtPa zP|PCkd;|+mV@b^b&NEqu#bA~hJxHr4$X-x-l##qVX5w2lKaz&Nc#P40mFOxWUWUwu zLyOk0j+qTmWMLFG);DndC?);(PJ$a86~MmiIcSeg~R8icq>Kv6sr9>C7?ZB$v)F zd5_&Xes2YOSJ__tsx<}kD(r!iCM9fUgC6wmre4v%DNpOc4jDK!bj6kCZ{`g`%6prb z)Jgf9ys7d>UZAu3PRJEE?Za#t#0Z=<_Ie|Lsp%7k_>TZ3C~CdH=6t&OrdMuUQ-Rn1 zh@I#Zx^Lq1qk%jaQ?;1~+NTj*$!RFBT>2|Q37yiYe~KE}Ko);6QvXdSFf~wymyBiB zhiN-k@b_E=%zN{NZ3Kl9lx>A1uS~{cn4|)&9LP#^5KS)!qC{`f2d;^I)^CxW`?+lN zu7~gC#X43cop0Ez-X0s)#Jl{+l?l^A3q9p46$RI&y(JxUwu!j3NuJ?Lv&?VFn9L#~ zhT^6?l`S&!6E!rSY!0e9(7|~()yeFQB8)u{wTUmz?A90sepL@4=?B`!)YT|;j$+Vb z51uoa2s$;h((VeL76D29Inn+>2IAx$z@3Sq3PJO`g+@Q#DDlEqS^XsceV{u>lfS{t z%e^1@HWG*@?5HcVA++a)KPd}gFmkkU7(1x{+&*UP6KqvflIt%T(Jq0$P{$s^V1FUe z>)fDaQPeg+XX>JnjA7#-(9%{a@@J#2Vv{`K;GL?^SLlxU(*5weq%Z#$0HHu$zew9DSjp<8WX}WjF_XhR6;oN=DDAD52;A8x z7;VY4g_!uTJvdpk<=7C$IR!yp=2YCJm|@M`!Sq>l1~$qZP8NA3z9yL9vGQjcQr56I za(V2ejm54E2b`>kruUaLhUae!VMT3qT;a`O0lm%_k(M_u4{IJ(u(N6Am}msAJ=VVW z8L5U`x9=^iaeKWWgg9;U`7M2AL=nZdSF{1@R7MRuMZBC*&g*m)CC@IF2QlR5dpKCs z`VuQkw-{uckGfnJNQ&!Z@PkcMA^pDiSthF5_`};<4nN8Zrygw3;zcq;9M>%7@!98h z0wBzsV;)NPq-mW>Sjj;c9o9pkw$9?{YGE%vg)CLR?BK`7&;h_9O!LZINXcW4HSIPU ziWf^yhCj;-oXbjMneK4T3ct@(Dq}I@kgHN#YBQ{14(+U<) zI!PQ02*=4zpMbWyI9n;(a_13lKZkhYpPwl{%;xT}rk z-fG~HjP3^Eh0d#-`Y|h9*JOg;-IsnfxpHt)PlDB^b!AdFYWj5*BRk`>2JS2j8sE$$$=1B{;GbL58JO;=V|r@c#Us(deQ z;2gJ9)k3zMNweU0m!hd_7rK(TZ*BwJFt<8J;Kx2nSCNMghbOT`3rMP(Ql>Xl>Byv~ zbwp$k=5vM0C7`B`w=>FOEY8ixy6>%HX0}#EJ_ut@7X{T!ib|PS#yGizhObjYCv6@_ zs6s8>8jG!URI&?wGhA7_?|;^`H&&Zej$<4i_kH7x;cwuRz^8N*{4)`jyj~9tY&H>nzm|YmU%1a7insSK27-^o)|K zwSrxvCnM9aT;lO^Z1pc~JCB43{8w%qjhvHDUyY?gw1TzB7ji@)UXTPN$n^*W z9svbN9~)lxY-33$(OaxqR;CEqbF4lae`Ta{QfQ4nYi0NO*hEdn&_@m-bQ_foeAf3E zsAX^s$ioRUD#%NX6qp4eUvrKFoZ+)nI!oMC0&?iBWlS2?AiG-TxC3&5P7M@aT#wknxc>J{1s0Zj;KJcEyt<&su1 zC!ct4>RxbGW&2dacDl&MJ8=#;!iL>O+Q-WcN&Q@dZhaRwyaDoDn^Up?x zPi|MUqt`rBt2~`ng3;%q($YhL1SYv%HExv@MAlOi;k%Lm?5U3&_QLwSIb)hOkDAYl zSe-GbLKe1jjU&w<8Ba-o6}|ugge5|)uv!yc-5GLC!#u8ZYDF=_O2;QEX%6Fq**9Z- z7SU9SwPa`q2s{#j-8UkGhd5aZ5CHO0#!I`Z0uKiIMjo zRUrXL5DcdX$r;KL&1V6yOXhvK&}6WT`xp2OX1v z%28y^jjDKQOWyX9=L`=(thW029Zt)2IVZ$+=fA#v0;Cr^BnJ>MuAM&=@Z1|&RS1$e zo4W_GD=dzYWX`z-1$7>trjo9rSXf+7h8>TDXOgSW)pa#fnhI04ou>ul@>T}A)EAj2 zY;?f;w6KSaJAf5#$vsupp_K6g(htS+T#|NZqSLi?m2+*i zyJ6>sw3U^lA$}(^$HTcR0-(BjShln8KY{4C8e*a&qSq>eT_8NmMlQu8j|9-LjGy-xGA z<0mAXt(CkuyDx79;3)0($41P(+q25v+u(t&4|_+!&M>A^NU}`Y+UXe_O>@U3SS^vV z78e1>nOZt$x_q-~0QSP(2Z$*=G6>_%a&6|$xIL?r2KRf8l$hBjN%B-ZLs6(M%$5sm zZsc%5S33FzR>Qrl1;eo&)IXwYDJX>TF~s*iN{cSZE)C+8l^j);-9txJ;asDg$98gE z$924p+jEo|;bcbk)IEY;8V91 zYU~wqL3E~bi-2hd9;=$+hw^z~Ir8n4$xUU9VGIdX{X? zt>XX|AI)%Ey}(0VA$C49^<7<+Z?3mZ8(+iPcphbPd+wdCWow-Fklp>yW!0pFV>X$? ztZs2N&v2?Oa~qP=lHxp;b<lUFJ3&Xg-=-#45*WeQeE9TL ztDPXG1@k!kMC{1&UX}?aa%Ja(RTQyPhZh~sJ+tJoWP7H0-tVGGSaZ2WmlC>?vj(Vn zU*8dQYa>QkH3{sb0WY1Z^N=?-R=G(#1nDaZ`_2Y-`7h4>Ryn1W%^!K`GRCo_`!$z~CY-u~92W4&9DB8@w=J@x zM%6nHaf(7VeLWSz)lm_Vpp1|*a#p)oSH~qpRN_Aro&#~%73bs4I;kGC@Im{i&9H06 z81{#JZ5@==?YnAF=RCQz#9-%)rs-`JK@Cf8RZ?wkGvVP|YwBUAb2c%oiKB+$8&|UVYvfEMvyY;x)zvm<7m=Ktt@(YJ zHyzv)GP0JgmGt$D1O}WEJ1WqUI{STHK6f@JmN)5vg|~_WS1*GQz<(K9$~$b9+UmMD z9Ebcv=C;JtWyy|Zxo}b|>789gS8bVtEFTH;E00`hok>|F4g?mQa=VSim+v~t zj>|Fh!J`=}BN+#UUC}GG5ve0^#uFR}I4A^zkfKttwGxw*xe6fiPB>By>ZOMu@)72K zs8o}nG>jxpasVCCat5`-J&JbVj0A^*rJ`B@GNgtREP9CfQa57=%7Vgq1ALR zqQG(Q=&``T1!i38T~&dc7WpV4r!p3R3D0E>?N)CrX>g6^n}9N_$8gAQ9m2X=_uMO{ zuXin{jn4z~!osy^yOkc8{^%+QY*TQ+LCzAk8zVGgy1Vl#G;rSbKSA4FD!C2pRmeCv8oNm}K6?*rf^n{Ea?8##%84W9zTD2pl zI!U6VLljqfc`Q}c1ICfAk{L6A-hSyl)Yjc6exMZ8R_bEeWs{k9XOH_~b!{D>bVa(4 z^xe)mB&ja~7|%Vt&*Hizrq@6i+Nh~PEp}P}^1)eoQywO`D>^@?bQ*fq4ck-99kUN? zW4w+$`TWza)U(&NONB#xCP|jV3r03Hdaj7eEj{w=$JD=dJu^WO`Df8#>w0=hX1++8 z+?;^GFdOn*`RB(6MhtIp%}9a@Ezk_=YfWy!@Z3>0(~=jV;Pg{9;F$BPo}-yhxz|`6T5@dWgkSJmc;L z0yFBcp_tT`H#x!Kd2;Oav91k;6l`lS_bk}hlYBf=3G?-!Cn3Jgo9A z4v4x%*^j5DrekAhF4_q`oGn##!g)z+Nyo`XCLw{@``oOEqHFN%XCYuWvteSXV=_-ap7S^9k`G?pEa6vblR5&H8p~oM|?!_%Cp&Qw6OL~ZmSjF4DkWSB!ySJvYtB01YiKuto#nNHS67D4V*Z(;GtXbxc+&!WA2m3$DQm7R9|Xn3`DcWe2rgwvMxQozrNlLm%F3LkH1mbbnN?HYPffX>6=f`ptd4gKHLWVpjXq4W zHSkk4Tnv2s+TKHgr|T*xr?$3iAA~E#*@C8`O!@Mw7uXDznVJT6h4eZZIJHNCs)}~m z$DZA!g&7!A)Qz(ikD7tbE9+NB&E@@=AvbYOQf4R_LRR)d0{h{!_drYFjAbYpQXU51 z#d%w(>!TIJ1cTW}fC>4j=JdkDl0qG~9hKuNBE*%zoC2}jGUBBtIVwCHWbGvOL*pQJP05hRS}-|Mv$A7GP70Br2CE{Xrg^CV$_N-)wQLdN2u>p@HicwO z36N2PjHTItaoun9c8|I0>ys!bqC0c(zqo3vmflOk4n~7Jc|2!@EG>M526L6^TVF=B z2A-vCx28>1I2k0I0(}!*VbJfG$4O<5Cbxim%IB7{F*+lq++gVc0A~svXVd8QG#4)p zrP&*C=DCH_r`L@mbZr&Zo<>9@!ye}F;ol&R7r1%}s(NLobiIA2Radf-I&x-*F8jca z+^;lyMs*^GT591In2qq-sN^0KOGn3#+Wb}zQ{?ueXldseCn6nLsiK!f!D^<7p|P|z zm?3gI>uRZO?V*tE-bAN9eekx;y7;n31kTGt!8sfcl80D1mz&Hq*7iDAy@8>uZhV$_ z6NKL5ZB)0V6jZ5cDk_U&f->hjjuv0{J5&o;Ht#tEXU$xUR5~KqAbdV0(xI8d$N09C z?G0wDzFbXJPs2>dZgF>->&d8C<7~qYk!Ezt9etv*fCD=K4(zbs$j6%NT~MN-hMQ$G zS(cmv7b9qKamr~kJn-DgYVbz{>LVK&F61ZeYkA6*fukgp(lN^FIcbdq9?A&;F5zT; z3Kw@OP^Lx(4=MQ`sv>f6*=1X3D(kA51Q9vzdBDKzrL?;tCfy-e>;Sg5s|C*Z@f96A zoI8?o09P$P)7WSWD{oF?-HeEV{*`Il-~j~$F~ZD#$j1wZKC&=-Vs>HDxhpEvS`N!< zm9yNd8&xAY@R)c9>OfWsN=mz=ji$HLwg@{H0gMl-nU>mdA)2+^6Ow$^)t46dVv5xX zE$rvxoGv$>a^&k+qaF;(8arKytb&s>Ri0#+`uZ(}HvO_qT`uD162>h%B!b(MGsLwI z_)-Zd4Z3hE2GQpIn-`cU2K>C01k{g zCw{h1h_}oG>Z{trQZlk;P{jT2-I=5wZdIK}s4A=zikoAAimi@r;y&p4Ei^YKx)D_A zL38N;63^=A{xPQZJGj$FKLI`LxL?lhX}rYmJTV{3-;*0lcs+woW$Vi(MO zNdUmf%BbJ2wVICbS(gxG$@B`}M*jeGqHHm{zYoXFbG&IAJdN}veNJm16HY)YGLHm+ z4|S!;@d?jzw-%V(`I_KKYeLF(TpHimVQU;%xMTf-?(bcRcX2 zrjNvFB>dCk+R9cBK1!mBT$(J5!>FbQhW1%Bj0|I1c)}V3oJr*&drQLtklUALCGq63 zfv@3d%wGp@HM_b&vNoOyzzu6-&NITgGHln+E87d0Mp}D?RH$@po#c1itaXySXK3S* zg;uVO?3K^wla;1E%(6@9o*Kf~+}9iosp~2PF!-z)1-;gs$T5z2?y_Z=%R*b+fVw+o zd)-maH7us1QZ@a?RkT#LDmrR+4~sce+oW=SnvKMFIV#NO2_%ez_eOE}FHa)=w2pjT zvf-wyMLf)8ytZ7ZH4F@fsMSO;((Xs9!nI7-Q?+h}R*I?@5J@fbURq8wR&+^w zO+>B@JEA#h;lt#x=4{ayKgG;$>^%~V?1j=7;ke{2d8m?Hv`fM4y%d||^71QYL+89v zGsT1#MDETVmd@F7t*Z@&U@?N{IIVP78DfRB?-_4nm75&#nrBA|0nb}*a*Z=SB4SNb zSv<}R&d}l-N0?h07-wvI=XY?iDd3b1t!X&nX8|KwhdTk=Ar(YP6h@ z_xPRt3zy%b5ZB1ubCuM3bnxvvG*}r9KhU}6vZXNH9!F<#yxk-J0LYR%vpd6RG_7q! zHLMTl0JK1u%w+ExUNg^H`>KQ@8vgKwaptExL+Zo0}9ZEe_)( zc2^5#nV_6_)sFE$9v8;R?R3wfQoc%cO)U3UZKPp2SrKUYsn{P?KHT{Z>hnE46}}u% z!p698FsnCPr94^px%nrsD|ojhaUs`C#HY4UwZmyIE#+#pJzHZw6I-+ag)KGa5i_3| z!v|$p)OS%mQ{A{=oQ_ubvCYqvbLGhh#j7=-?vd|4iob9H^i^0sMQmy=O^P#u4not` zN=>$B4&|hk?BGr^M}wKBxH&arNS;nS%Ea0a1!mPancUOcD;h?Df;`vOV}9OGoXDG# zBftXzH1XX%GEYUJn3)JoLIb3Deu?`BnuFfs@+snWIA<%#+RolSMo!ESM`f<4J6>=W z&Cg@_%D}3RB)BWVS1hEe_#k7!X#^*-Fzn$#*JB`zESOw8mc&ru8vwx^r#dE)#uDYe zVMh6y-cM|-(4!7uSnl`=KtS_LAQCjWsuw>G*@dEf5wyu>+;fD4WZ)(TaoIc^5LBWn zz_%U3Va8M3{P`xaj*;PwZtVFgw?NObS*mZhC~dT?%jG!j*?Ol*SnNGH(~-sCwbin> zbKU^SKVIsO(j6HMhS2RxbclP+Lw`~T1ct4SqPojf_oT^h0ePCd^r-LCV3fNtwMMpV zl{+Vpd}rZ|B-61}UFT(0NHJ4~aNP3mnyyjUJX+&fKIsW|N0?m#+Z$nQVW2bt*?3yK z_Zo{nheM`U?nc79PSMv!&jOjQ4Q>mILC^LoW$#CLO_Ep8nHtud0N%^1>FMKiCN{Jm zqRgm$8zk8~S;#qSvC8GsXP!2je4ToYJQ}2qSEDOBA0-sjTHe)H9YlU$oRSZ5$C|~w zbz7!dIyqzMj)jEPxu!8)UeP0%2*KJ2;Qs*BwcTH|(_%`iV_Ou2aj<1@#>rU8H&kyV zA2PnDO+PeuNcr5@TxCVp7;80c)%QE?VUW0oNaMzSL0x}J+pN@L3Kq6DZRY?ES1P8x zPYYZGpAXA~%Dq8TGfa6qaI?#&mU&UEGQ$*2&aTuoZ&|k z@bX-EE6~&6SYt@?^X2nRvQRxjjnV;;!X%gC=ysl;#YgZ*6jZUZ_O|>* zbL7=R9UUu7E4)#!qK$=kr|(EjMMSj6PSo zSAofMxJx9KDmb3`9UDtVMhIShT~zWvV>>ZmhA-HuMPhWr#Hc4exler*j0QLw-J{Hv z9phQi0_bTrpF#@N!s@EVHm=2k6b-d01ho#m~^1I=qG;hv$*nzuLyAy!!_S$JV~WKuEJ4mDDeTuyLS zWZ5_)0Y+c64WM9p1jkJ8*;rMbu!>w0`^=Gm00$kiol9|=N`|rT7(Kk!s;#vrH(cGN&SvJ=NqX^xWEn4yV74SIxQIoWvFI7aTxlaWkWG~^Z_ zu5Qh!h{q1#-MM$jssy(e)Ik(ug7Q`L@KTFxu1D@vUbKaKt`vUdD;i5?FDL7Twbqr- zik>){_ZICK0cbjT85QI?-wwl(k2JdGsz_>S<9={k{A7 z&t+(~#9*Tme$fi2Z*i!Np}7Q=X@$-d?O-|Q3zlk;y62N&OmN~!`K`qi#JJXD*FRn`#TBa48)q1gtov%|pU;@7m%PbPS((Cyy*5caabc9laI@ zig+3Brj)z@3CKQbrEJKF`Nu06lMj0>apUHYOd4>pnf^Syl_?R)x2|Y(KsS&Uw6tt3 z;1IGy-6L_kzmmlVY_1OHD#%NNS6Kmb9z#`fMHpp6 z>88mJ7~At#Dp+A{5N^<1*KPbWm0G-*X>TyUI6Jf1aqe=F(J3T~UG=g>EU?CK%#a7k zYp7}5=J=94HH`f7xB9|3=_-6s(}z2ZEeNltt7m}xHNbEI!uBw&(YAS6p4LfIRM~L@ z8;5y0Nle<9n~66bI3YwWf@TL7fJZ&ksBSHWq*B#J9Kpuyl$T~nIdWJiDkpP%C@G&S zkDfazYH2I{GbD^4H`Ml6@K)R-aquc5WJKY+KK2x^bEX8zJdbhnC1EMPjpDux2`l~F zZF30+&0@-HTa{F9QI}D*`*AX@Ol=eC2Syb~gDIW8lGH@1xg34J|#<{L=&vLFt#sopz z8{6oq3>a{7ial0LoE2!b#ecn+su&_?kOeZn>n*}z;BLlwB&-cnW1P$kPS|pJTINMP zvJm%FWS>I4On7n5DoEnbl4j?2tXo=%nin4#sQbIN?rYny?D z`Cw@Hf`GJ?l!-Bs8)8?j# zBk;(N(P6j~AvjsG$k84a7;ttRJk^m|k^(S-{=g=t7lL--N(7E4j1^_k7KYs~w6yXx zhREEWd>2cQ z9gg2M)~?K&2wfwaxBzqJxqhLnsjjVTFv9jQI|m~HdwN`rQb&)df^N=tS819M={F6b zYfFahr0}nI$`=ZELk&EVIm0+;$-<)5(!QQky|Ogt?!CBP{+lN5&kt0-5euXMmV?4? z4(tDF}6AopRjv7C; zcTF*A(t0+TkKbHgMsP`#Z)B9U(vC>G|shhrMj@^eD{1rIsis>adl;O*Z zGM-=gz7IndGrm=8Fvj9MWOm_Uja4B9(>p&Q=D55%rmknS7n72#a9$Yr2KRhe^=z0Pq1f}!6%#l|XVT;~(ywzPEgR=D2kuh&## z0jIM{wm5At%_QFjRjSJs6b-oDC2xxHx;Syq;+;`fEROrcjPZ`ZEN9iatd~miJqMsm z9vlb6v&jekyO^crc%i9w>`qyK;ah8mn&0i zfR{Ktg0t#n5fB=2jux_M8L2sP%Z*iBW)^G!xzUUedo5X-YIk_f1d@13Z=JK!z~_;i zV2_f)xm+6*?t^xGg5qm*sOyVKBVlm4nz-Erj2Lj_kgK&2urfH|19T@YBh5uwaCoQ@ zvBzmVqR~03shS3~xz6BZEu$TgD%HLVUXQ#oD&~!t8xBgcYYS{wS}JRV1UQcP6q$9fHQD+}nxm z-wKpnGk3dXOPn|bODnuid#LIncIHTb949le80?~v68``US;=nFaxhjyrUD^?ps5AG z<)GUpa!Jb5J|Eg7t9qyV=#L*UvwrqRfDU>-tzvp#107*v#ggJa=1$j>Q~Gi0MY zgM*bEZhU7iS8N$`m-u6n@ zHP|4nmBE`B@?1*~bx*5~Xk>GkNFG>S*x;tAoN}Jm!P4mOjgDyBGqA}({7S~vt&O)z zcgA-#fs^xEh@z-rSd*tAkVXEPNmSXle07FI&e1fG+YF|^n7|Xypz!_eSmNuF=wGN$@h(mqZhm4-; z3Y&KD>0=w3dkk`*r@0o!#!UfdBP5J1=Zz1$iNuZYiQs~=$rl!FiCW;S!&6^ssS<+c zG#qX6!mv8IlD0ayzU$kPJq}bgqLQc(!qV&s7qwD52zaAza&3uC42ucFhef>v#I&F&vF{{V7Z zgnl)cw2b5szJDaEP^JE!t|MykFyMO1O-dHF7#q3a@&+8S}Whm}s$7Q^Ei7;>{TRcWTJ zbu5_;cOxZoY9E^g9rz&eXWaTaTvdB#ZqPfcWjwLZN<7aBolMB4!^4%5)a|)pJP^KP zDrdB!z6$oNppsag<{S_S3!FhqJLGd_d3FyOU55JrbPp~a+PTE^&4Q?f*`v%<-v&N#6dRrDvLt5Xm5;Qw#F5=u zJ0hAHAamP*RAYgGQc4M8hB24(<-)UcDZY$~W|ix=f+m}(rD$%+I+KLQ*e8m;Z#SK`Q=wHQBn$dCXOaBj&cIn-X^K|q>YCh zoT@IN)H608bYlxi;N&jcDYPRbrm;FakMVU$c%@!uMYpKZ@6Q&z?^jM{}Oz)nM*9$UW522fKleO5Q7uP96K? zY$J2re)8K<)4juZD*}$6Cob+*>8HYAZ=&MMFLfTK7_Fqk4f0KvLA3mWiO(U~BrIC0 zrm>Frhbt~yFD9TvRZ?3SaVt(_l6f#3a7v1{yLl~XQrkm-4#y>RZQB!>OMIx>(81t> z{Nyctbzg}##`nt3mF4X$7(4n4A^m4TE|L`Qavrc*-LPWF)3W4Ku22C}<&1HMxzrv;1|S+lS;o4m@!vgp_!jvzkp^-O5k?mW_4 zAHFwkBa~D-!hY{13;hj@sN`eSVul&uID9dsus4hE5JQbS*o#38U0C?fA3U?BAD#(S+y z(#26RXlt9dy6(rqIs0h2-Du;Z(@>U-xzPj2?zMs;6`j>1hc%;=UoJ16%}*G24QoOC z6pwo+u)dvyZhJ;POCDU#nW)AE7N+d^B$7D*Ey4bkpfwd#m11f+Kp^rDWLMRgW|fDy zc^FVrMET`24nbbNC^$h;<;qHOre5^5H|fbuMeayNJWV9D4oUpht5Do6Q&!1KZ2^$8 zfCpuO+m%c-{seKB4#aw`JyTvJ@fg#WA4NpBOWbR|l|7a0aaf8a*S5!k&D0#qm9G_H{HGC51apcU3Lp&kbVCNPYJFnE4PkW{~dOey2>KnalAqjB9P3eLkAa_lF7PsZK? z(MpvHOdzL@m;%5Q?Q>oZ2W1}D9_mJXAM07pSHUVYO4c;AWuYkmapaii?EsH7*Ua6# z=PM^0G>u2HU(^Q&NXDNoMpbKA>6dIOhV46}e>0CI@R>IHI`&A6WqO>*T zpBB^DDob0A3dve2zRG@_>D@)nCf3)u!xQ^A%!PKb=t~{0snsou#gT&`IB?VIwi8!H zK}z|bf^)Tv1Ash=!nR$XEYA46kMu7$Cc1fTBd-pTFWB4BOxm{3uwlHnA5Z(%wmd$H zSjg;76ZFkS_EfdrME+_4BSWT)bCcO~?ryBv!7dTVHB?Py!cqsCk4{tws>V(-Lfq@9 z8p^%FxFuEX@))XGN1FO9Psyh|iN79X&Vf@Dbu7+FsOP35~DY*O|#-ebH6p_>P#aO9pcf4U871J%% zVZpdO5Hh?yb~lQ2k=dk{_*tKMaFUIZH?eBww}a?CMGYMr|Np28ea=OYSm2e=!=`k3Wa{1($`6hck*7|h8L302U#t# zXw{zRMhG0Cdnb^A2thbPMxN+Fp}+T}EJZ5&}YvP^)y^QfD|E*KzSrVa@qIzBt0 zofE$4^C_{_HOFap za+X1~hcIP$Gfi3RW14kl=F>p;O^62IeHQ-!{4U<@<&SPWm43l;_ezju$Y~*T>N)LK zN?blY#PO0Bos~Dq>|$zBE8|qM8e+odXB>?1t93W3s;M04*@2%WYr1KxY=rD~&`QOJ zMbo{f!K>Sm=C6$^IrCd9!tpyHF}=C^k0nRdl@Qg)=e4aK7|9`7bu9tz0&aI>FDse@{cP~l9d=yRBd^4TyvGOhUoaEWQ_0Tt89_XAfLfyvSQ(% zHR|NWNa4qsr^$4JtVr#}!Mph(Y(>SjMmQ=QGR^^2D}76x8vg*?D<<_MkVMM34mR?B zVR7x;>7B}IO3TwMwwj2uCBi10atgty7TZG{>Ux1AfZ(c42BbBO+rPJ=Q&e1B_7>+k z3jPNk!aC))f z1yyj3x$;^H>w%MoySkFTta8wD7QQ-nPT_*O@WC(G=glj1WK4>A9yam`^HzI(Ifaef zJd_kR;2H^bapbcqWNkcKf$~g}-R(p|8y4eVcc{Q~BR%t#k7yFo5i1GbxH~D){{V$Z zTzh3?vL6k=4&_Q!8fBEXJ1q8!x>IoRWEc|+N%G^#SM@wm(_I{C95$1Yk2M^1Z-!y9 z*!Y$;Z054!$H?#ld04D&?#;KZjjgVPUD_V@{A_Mdsanwlv;v^Hq&bq{0v6WYRw&{# z)0{bkXXdu?Lr174hIfL-Ov3jcBH*)faAxp|k}K6xx}Mt<(qulM;G&9G+o&AqFzIJF zJoDQDPpYY(Pe(|{4m?L==&-37C%Z!9l4;QBX`I-EGjT3A5%X5PJ4jnp zSWg9@;b`pCRdw}Fo>pX@cw2gT-%DGxI}QV%>|FV0d?Q?Hxh!?{o)tuKor#A?g)10(8t*ba|WM?JIk7agrUA_w~wnuO?^IM5z@n9q#TD0^d^-+_- z!r9Yv9D#GnG|eL2nU=XXQm5)n{<=H(Ix%af#S0m`CB6PiTWxqS zmOSFFjNqW0N`j$az ziPZ44;GbM8^<}lj2G0>EJh`}4>*kw=q91yJjSp`wXg!McwE5JPNZ@$u+>H!1(>22~ zx#P?gzOvKJa(kN=3FGip#=z=)vEv6gD#xa4%^;jcKy%xGR=IJd8%40SyOUvkvWE1mR8-!Cx{{R(?I39Rd;!VCu&dUV` z9^K9J3pPBp+2gX&+3L>bvFC%rutZ~fhR~~)JBneKe2UV0sbe_j)kJXFF_iZ-G;l^2 zKGUF(xvuYp502=h3_08LP0u5g=j?xT*-BPTnXRa3Zc|%U!bmGv`JUHf1T|zp87Lqe zW4ebCW?Lr@ehC01l0aXvrz;WddkNY&Ok-tTX01O2tYCg8zH*0;Mt>e*4YIA-e4aciF= zB^Y2C030oZGMc)1HWC=;{{RJ1p{aJ)%F)hnR{q&j{{V|6c1By0uyJXVMy~x;x?gsU z{{ReJoGeRKQ=_Rd_ue+)MLYaSUgO-A1Y4q?#4YX0=!)Z#l3zuyOKlNU^OqC;v`E&r zxxnxoI9Qrqp|5pAS;MiCvZ?@eV_%*_RPE&Dm+Vt@-7%SukT(UTbI7*|zJ+x?X$II` zpD~rpqyd#K9z}b42|pW)JgrjqS-pm@Z7|8_3cXn=mU7@$M71TX*gV3(ammW+$BSd! zWtTqqD4YE?T=-`U-HumT)0JkWjT^>B0^*jpI8M;h%IaD|tVS3d?pez6y;mCS_B}5n zlu~7~X~SIjo!wWiaG4@`@&RpXAL7Z7vFf8}fz8h(Wq7!$a?ej9Z=$5xMJy8%a4 z+7Oip3!C0cZ~@AaE?6=YVrDzJ1Sz&~3rWKB-wMvei3@Ptf}-3@uv6L}k(}W%*ac?Y zl0S@NDY(ZuNIC3+P8Dnrx#T|yNy00%wU=6g;SQ zsP&+WY{vogQTmB zAMF=e!;{;@*>Dz}_88*H;s# z*wnB%kO5}5Y)}DuM`1Rz3KseYI!owLN@p)Hql{NYN$}$k;h%U=HsRqeLPXJWJ>cCt zUJ{n9GgDTo_0U;F(l3|&s`c+{DXASz)*j|BC@T5f6n}r(X}##xKqs{5S%?Q1>#(3);3p#$2%J+ALEtrn;(zI$}WDzPhYNw|CM_$7L zz~Q~-Mt-PT%GGS5wy0>(!_j!`HzQfBF8ri&4P>SngxP80ht5=_vO9hCfFZ?$Qw+5p zhazm2W}U0P(3_JscX`Uy%UT04B@&$y_584zmRha2ET4My1%=t2&jQrY)b|bGDj>=* zBat)ZKKzMlCPC)()ZxST$72aycU?hvO7TK)b@ZlNakBNI4Kh?U;CCAw3G7D#Gs0Wp?NZvG=v9GfGeVi17(3{~l+z zssImxsr79t6;!si;zS8qz)yXI->21p2AYY$7vpT~+MZc{64jA@8u~Y2kcY3=Np(EY zvlFy$f`|z|p?Jh#0BQIq0GFSh}E&E`wB}t)nX3My9m*2Jq?T_7W?&+$AHR&KK z{Nw!9&nJaxoK#sipZ9=j;hfBLKdR%xHO(v=~tq ze;|n7`dD5U&B6nXAbwRd_x}C<%5FFiO&ki=@~$Bj(8!2kEOP|94vT-SVmGW#kdRP< z%>t~`H~o%lh%}Bbrlgii!QZ_iR(^8mRjD^br=4QwMv;cd$N$O9UyMadjO0Y^CG4cTesF$ISmiu(&bqu){KC4!a%CW<-~?3Q+wg(6BX2;)nDy&7g+@dfbb(X6ygl=t za}!mRRl~uM>~C28B$%{EL=1^nZfF?}^nYu(s{N6IMvW;E-FK+=)+r-#CL=&s@J#V7 zPD(^rk=3X$LreSpH0c`(TMAHI&bcx1x1SyXbT9U5f!>GJ!wDs}FWO?ZZYmgZRm3Q| zGO7f)MzZm70%}IgxhlVR!;g0hvM2f(=u;PJX87Ld>rN%|o(DTS_>-NF>6T3ljnPgTrbE^_nNNtW46&cc`w`rDXXqREa*AVuvDQ?Vi_cmw4TK+$$#(p+7s$;64jcHZ zy*ByVdPFWw`~tc}%p&8T7mt~6dD$hIJT|3gLhZ@Tt2&BV^ABn)!^||^x*v+qadazE z>1+va?saQvW}J-k8P+dZD?e=R6>2j7DX+IWw`5;1+4L%3PW-Q9!6{Em8z9kHOIFZ2 zbS>^q!Y0W4y(&Oh1WpK_X-UBCn#sHVEB%ipp^I_s89;keSrmu$pQ^98PrPMNO{oAPvx z`+ELZomg55BHo9U|ji5Z*;Nw#8ew6F6)k zvCfY>w!iX&DbD*rpzN)M4Z4Vy>6RG!05+EmL1Uy0!aOZ;PDv(u&I%as^?8cV)LmC! zSaD-6Apez|@y?c2hAPgem#v-jaD+pgCZ?Aa*n_|2XF|*~)VGswkmg|UopI5BP|r_n zMpUO|soch%!c=Lkgooyef_P`$Mo%S4fJlk)xkTZosOtCj?IJ&M* z(j?-yJ~puyz}8i}#3L~asXm$U?0ymsyv;Ek_b2qJ9x?3xTCpd*wdB*P^ll0?^O2ot zV}?OuFB|E^Ktwq1O)`CJH5j04oX6hJisY=;njvMLknP$Ezk?eogFMZhYbzR3hIaPS z-==fl%fO$GTc`Q93y+V<%6G7==`2JH#OHRpo%YT>&62Z;Z~PXTC`VNlc`^zXHzBH${h3!#%GDGHX)y?F+5bY zL{E#6{})6Y$ax%%iYH@A*e#~?XB@Z8OVbQYdu^U;bV)PjP7Gk@TT zzL!bs*tVLG+wA-)k!`4<-&a5sla%`FydN6)E@d}BvWi?KwW!w1Up^J!ZfGFxEF6k; zVYsiOqOwf!`T1}D)19J(sGm1h>zc$v2-c@IjCBl~j32i~*D7EAMGAtX1gh%cy+T|6 zEznS9ROgLmZeeY)MA`&@eeV$+EY$hb*f6^551yFRtE7!KAMI9AM>XZUDh}RZ`vHxO z%vSD=&9p%>#GOf?@^EhS!uwLk>p~2X!L&+3hAwTx$_oy-sR=3@5_GIwPrJ0;;Mia=qHC%o`*OP(C(V8~Q@inl3d)a_NVPk>P z!C>*nSzSGG=*gk&2Ydkpne$4pvTc9A!*JqD3w%c4wUQK6nUx@5%!1;Op!zV~mS^Fo z|Goa*r$~`48+MGq{(79Iaza&^-D8uLJig$4CcZ^pnaz(MK#MbN=|7fzhp+E)#ZM|| z<>J~ofBed}rsK)nJk{H!r!XD|$syi4(h&H@*3RZO3hL;b~GmiFd*&M`v+TlN=9k5308#4<=3G*X55I*1UG zab%-!XR<|O-O)qTJ1ySRR#FaBjn{0XKLnfj8oYL+%1F3}fRyf<1pD#X`quBO8#DON z<@?r%$uvj@i;LglBr--uwL0^RYZ7J)u7B*uC8hfZ2tHny8%pJ(`I2*+l|%o;Uk9et z^YKb7)eyXnmmK=C!O7Y?j9i%Db$aXw(#4Tg4s&`oT~J_pT6VS%*71gzf0L`bk)>coKc4_0ppVcg|oC!r@ z*oxT}{{R$jL^m8CkATNZ(SH#9se(fG_Mr#7bx#y|=Rj-(N=na>)G;L4gFgE8?$#dk z6DAA@7%waE&+o(o4@hJbHi{~CHhkbgxe86$A@YTTX%nka^tX~CAO4gb@SDbriyfh$hU})J1`3pMKdL(uX&8`f zR+{q1`#}s;63e@~K=|LO*71x9(U5rUQTe<1?_ySf!fF0OS*FDyOW~%lAf-Q>Dl+fP zu$Sbd{X)qeYP@X!F6&)fTAxx1xf2s~{#X~B!y0$4b5ZwIzTj3tc!H0;cYiR8KfK|d z_D+CQaaqv^Y;pcFQKi)cVx-%n0=7(Y1&O9cTIJh|{NE686uI9f%df1V;e=77M@BOG z=#We-QT3KTc)+0VYoE1%#Q$toi3*~3&h&G76l_JAK;b1kYbPBw3kq+oIEPFoW?(N) zX~+nHruY|Hj=Z__kcE$?u|;f=iF*5GxJNiOM!%KmRuiH)iub$e|XNf?R&IS)w9 zAI6nBu-W^Vk7sOV;j@_;J$2Z2-n3Kt$rtt2!%pc(eXLq>vpHq<)A$)_c}b4Npa3=- zsDZxkt|}7)8G3jUN$=(4*H7*hBVtClJY3GIMJ$uBWW^c7fK>i5oDrM~Q&(Bag-j^L z{1-eNwKc-x!6x{02&><_t(D)NMk8Kh^F$9s_YAEwb+!`RuA7fX7{_ckX9p|YsA$`3 zek+#Ev)8aoRn-(!Go)tPr}Cq>AA1Mlogxf+Ypty+?Il65z$<;dgk?HpvTmR}v9ndW zameJlZ-bj9sRmZ!`+jI20ZqRTmXxo})f5KS0l!boB($G7qdw&FU&yVrxBfH zSuWc^j~~-t9qm@uC^0p3c16&qF|qZJdZzgRZ)d4@tryxsxm?Z=61GT&Ir~r?c+4GC zjDA;Qir$@LWli_cYsn&?H>SIe!T}RBm|U+9qf1Eq{oGQ=%Q>Ebm3$(AG^a_7S%e}( zdYn2Dsk{sxJYw6)CE>5g$$tc|ZZw{6^94m_V{H-^*n0a6s(xMJ?$4;q=mak&9)Rf+ zC&q1SD-u@j;kUIfp|a*#J{b3^C&{#eYqb_E!M=Mx1E~K2Vi&b2Qt^@ml9ZGcUdcz& z0a>)xx-v4p7xNmCjf`h6nXcJ<6P(NhNo4Yb3+}cQ0tgTh75#xWg`N|SWzc1KM0Kij&=*vz-J7t3m|ehPU0r%9MdYN@pJVOKaNU)qrH6FZN_JZ)DOV!%0Z zsD;sCQT|YPNQ`4i^GROPv1;x0b)j?Z`C)5jAjCjds;$oeH>&TA8a(?`-TY4cS(9x> zeVUz!aRw1ll@&pbakC%5Ir)6avt{DB)5;(JE%u>;mQI;7({_eM&mb-5T-ITRtn_;N zpYb=OOea*mKvJx0#*v_Wlu9VM;)^P2giW0_C}&}W@oa@xe*8-95#pcnhY2ZsMnkyNaA|Ro6YWgRS2Vs{oH5z6s4Ncz4RI z*AkXaN9>3TuXoS!^!~QV1kv?V5LV;yHb=6^SCo2>zb$-`S{_k*q2a*nI7TKXBQ|tM zFp&9H?CbJk6WV&bC6~R?*iFCKi^!Ldp%3SVG|%_MF^8)o+oW>yBJMGkHRb-zPK< z%NkvpNth6!@1UfA3Oa2|9N{I19$b48R8}1?giT0D2P|NOT9gP^DUK=On{(?M=-75W zD-a;!FB)44jpOzVt?VFc)}m*{dRy{YfpFeWnAV`3!{H$%HD?`~`(ibU)gd}wIeOBZ zH?-1;m7J=jOZZApjrOyek~2Gg@ydqT5(|uo{H!_X>Uk5(dmMlj5O1;- zI9Z-1{R6Ok;P}hL%uNF)NUAvRQNJR#2z+CrNo|TYv2YkC^eWqUX}Fa*z^46#SaxNf z3vC7;;xZF^Yz;vA_r4P)rIP+y=y#kGyvXtPOT3jmYoK7#Gmotjm=f&O#gChcmhI-1 zuqp*v1Y2>yCdQxj&Y}8cVi2wt*{;HqYh%qh>R+yhUPpje2w*j8qscQdK~kgbW+hB%)1NXRIpxA3%sA=GJH>XaH^j5w;n*2O#ClI3sd^*%8-fk!;Sc6|@>fC3&JdtcMK{**05?76Ej+ zU&y+t=Lxv?uz0+^{mI ztfvePCLViz0R+z)Z=8wpr?=LrvkA^F(cf4=rad&2XBE_uXu3$~NqY&NQgA zAs$Wzz~iKG60z8zG1$)^?wLE{3eF$PUgJsN^bo8RBiX-yBR2V3biDFSJ3mCzJ7iV& z1M10+j_E$F4riY<7l)OYF)#0Cbo`m@P9S^b4E6I_kXrNm&S%J(PvXCFa#Dtm5AP(p zyLRHWXk~4mxepOf8)kOWZe`abAw23zyjQEK$$H_CK={Yi8Y9av3V>=NNQXjzGc`=kBQWmA}hwQSk~Fe`XqP@ZXuL=DG*7N{k} z)wxoY(iUnRIGKn3(+_1bQJiEw--xSb6-Z6^RMKWM=ziUcFs$Zk?rmYJX*9c3%-*AE z?l9$?KAqK`=?AAwOiQCKip?Sz9k`O*sM5vST=+wu<3_yFFoJ zwFU+bDNyxcR$B4?wVv=Yo2~YBdNbKeX@BSNH{6+>#1wdVIJKS*yuU0+qkCU3o77`! zW=#kG8J^TyF^8Igtg6B26869l7^Bb(Xd3~Q*LF0BY6*ViPD@5J zhdJy_ipTz%4tau3+;1=HIr#7uwWz@{*h6K@hH^-SH%2H*fqU@F4wm0)VqaPBhynQ&XuNgR_^r)&>{Gj$xLr3h>{I{_j zv_!Gp$mFG{veqK(?!7sR;I~@qn|@z6f~h?6mN;i<<+>Z_{60M#!L~<_3MO=4+|=_i zLr}bk!>`Lkp+L2z--ggQ%~hUd8%LD}+l1Zrb)))b)aWapnn?-y$t!BHpwdJUro#I0 zUu=}<7@m1a`R6hwFgeJ|1Z;3bt47pq!osM6-M}p)eU5-F=zp{G+YPxU$u3dn2+vVI zW6P8*sK{M<9iZ4Da1A0AThHkLu6qWJaht}@fd-Oaa+CGy?$dN#+#*LcW;m0bLin7r zi_$-~u1jARpMJmxt4Mq$5*{x`@CnvM;BWL3{~g|$c5HUEu+g_9uA-Rf&oK?aNx;eY zDuhA_MU|J=Pq!Uh5-0pY2Ey*bI=;20F3aI(2E>u?_JWm|pMi2iuvN~?zIl5gFS^Gn z(Qb1ZvE1ex9K62N_Z|0S?cHKJ717s95ty&)A<-P1(&KrUw#43E`h+KKS=U?X@wI3- zhdDLF{`R?nzS&2cnfR7TQ8zy@;u#mmnk(HJjpiJv(U5Q_79ID8j5Wb*+BFu{En!{c z?NZrpWVQ-?o<%di&;f7#fQAMy;br%p239KhPD-Wr2l-L8F9D_TnzD5P!@J%{xX8_hMlG+mTy~cG z)$-m(HW9>D$n84;pQwGTh=)-;1t!B&#b-kzHGIx1eXXlSEhlOXDh|i9E)MGo+j^8O z>A8WFYutygB9tg_{BV^{O>6H2V^F3cooy5?H?iy)qn8G6-C|FI&R|4JN)nBb_=Y<{ za8KA{3NxBRU|?Z$kzi{%5i|T&1!n5sAEOrHCVjZKOOlu?tWwu8njw4n-3ByHQ6f3U z)K0Tx>b6?ei7hi_E-WTMBSq;*Fkk*28crkW;oPY9}{bFoD^ z0I?vSF}kDHPgTMVA?iMpWBa~=*)Zr3>(4K( zeh#Pv7uyhn--&&pePQ+xG)dfhQJ~(ecga->%TY(t>JBR!n!mSrt!OVW2IdVO0`)Ss zLhnR_bWzpRoqGE#pAM)ps*Y4hZHa6p8viC1<5N-tBEBN{KA<2H`pgV{SRa0t5HSHm*Xn1{YgTgXk3-W`{_W#ITKyvd#7W5&1ex;Lf|Pm zGdd0w058!hMw&ikcUN8ty#1l zX`(8dn-9azs6? z!J||w8Zsea5ZbV$o`2w8gjrU0CtKcb$n{n5{7ylRvU$_Rk-`k+qc2k@zn=6B{t)>M zJHA8d!|FzBp7a#8?mo_iexAwx%(?2g*Vl6$-d*Lrko6im@rHcj$(=1?#r6Dkv+-uG zmfvNma);}6)W8H{N5M)w9FGF6BE!Tuo!bf8tSCmr7?sv(#AaJm>KyOeyoI-F{w+=x z%oWuauB}rBvtpNxN4B=O-ij=1UfDE%cJdWXciL&W*OYK6jg$|LOPqupKVooGsJz`R?>A_dDl6rBG0jx(PO;;^9 z>HK%X;BX0zDrqSJrC=ihvAhAT)!M1W_DOor>WYW9aE}>l83HSUU)~5)XL@^(30Zmk$R@D+dv5G%uWQJGD{QqSJCafQ zpY+fT^OI}#0clpjp$+B3jso_T=lbjn0$nv~;ap$0yKX9NlM{clQ5GFP7r77jHytFt zVH4p`45?7u&)2HQhq)?#$rm{+o2I`!rm3d<^#yq%e*Izfv402j4^Y{iZohG+?&e+g zYjm3*$Vn18ea-Zl`qzr8it=C>?lU*{F~GK1k7pI)nulhv)nci(tnmi)>D-txGHnk6 z9irk~wKS-A7}k+_9i89!n-&VC9Ap|?erO|wb=a}iKM$<+t+!7C$A)6cg^0bLbS8q+Y+?kW419V;?H6#oD_<9zBX=f%3>{w|WAY&2-&e{;Az%h|+c zv!(iaZ8U<=U-#p~J-ZvKo(={3co?TACa&mltsa5(` z@@1i7jqtA9539z2b#WDrLQ|dC%y(}jGkF&<#9$P^h|s2CFuX#6y|$QP?J0|SAF+t+ zk$pZnBmU@N6ks9rd$`wn*Mn+S+ci%lVdhu-Mxw(-g#XuFF<{oM;fu%VjTQ%uFK%`t z7d0m15!(!Y{@EwHvdT1Zx}WD`QZjX_Smb=~x(c#?RCLSnjC}M8BZbJu;M~d$jYig~ zGvDjnt6H@`JEt&K)-6n-TEs~3l;OKnNgMf#xf=ABuJG*HB02b*1p*DSMakNt!n`JZ zy`i;Lb!LMDRuNQz7)v>k#}H1Gwk%{5)afu|pK4&6sG+5~hZf9N-xW`8%n6P0wNMY+ zCOI_Ke}C@`UQ6OHuvc!l6dZLZ#$-tbTz(b)KOiW=O*et03TIc$hpl)jAsqkI0-r}h z4Yr&SkZNao+|TzvaxT)sJVvkxm7XHZBM^)eP!}Xq8x3LerLN%3Q()iub*(6=xbS0K z00iy41^aRJ_jzYqmX_@X9k;dLPRo0(XOc15VgDPXpeuh@EE1R^dh>_-yDsmfV^|244lJ$LPh=h8TJvh#Rk-L&9&^ebW8`{BC1zVczD%zuiD*T20@SRQ>_Z1d<*I7&lj2TWco0$IEqr znF$K|8+gMxk1dB>pubxs6RYQxh{vdxr;tSqA}xC?J1mzpY_DI=urf7IOzF28aO#px z-|-07ZdAaoutV3>I`e}uf;pqH1rA($6JI|`q6rHq71b$reh~lTs~V&c3s*;)sydj3 z1GdLKj;}g?c8q&)dgbm4r>a(h6ymwuqX{auLMg@a zYQzKTr*b`&_+2uYTMqq+!P%Zbl8*%C;yOR87|(1njXYUR{i;Q=4K4`{z3T=-Nlgpn znxd|dTz(n(du;D`ryd(epQU*{(S(b+VxR_ukq>1O)Kkm)XsuZzQxt*QR3PhDWbDKT z^eaXl$lLSA(&ruwL@7}a4g}~6W5PSlQg3**1u^QIc6WJ50lz_Sg%C%OjC1TmS@khf zbWy7I;76Ma-6JoCJC0n7L%yk0P0hRU%kGbENItfW{WkU7fk;g2 zejd*~6Q(*@$+ZB!j~4ZKwAj5xHd=<%2GUl;PD?IA3nu0W6{s}}=Ey?SbPsYT2jP>G zB0CtwBo!k0m`VldCmn#LOF*G5@Eb;;9SNke^Nv#klTywuNOUoHl+a{*(Aq)DN zE}*NSgX59-tFg(NOo<|4x$)CX=(NeR4+9cPK|wJKy|sID9x;&D-WJYPs8RZ-6`hV> zYDibfuwKt-W#@j{(dwH1FNAVpuiYM*b{_>xdo;+oT}g0Mc;?ouw{WH59l6k}abWnT zU$6>A3d?`+ZqMA2(-I3-HT6v!0oiC;a~yuy?fi|km!~l!fmd?bnzBDz+$)%-ebB)M zYS8)45K!a8@XjeIp_bil_POWTQYru;Y*1jU98WW{OdGYlPzb%#nl>vm*F(k^{M|&$ z_x}T+yFm|@nqQ5un^S2evnB zce0KpW#};!R0qm0dcn|l8TuzZ8+efCtL85WzB*W|9LB1_Fk0sJbsfJfwB|&alD5hg|~&el7AF z0>MzHDtsZvGZO?fO{^X{0(J>&)U2%LK(v}431~=u#z5P{SX3|Cl=;*6NmQVQQD54d zHTM@?%F5sjv4IX7ySpcxmPd<)tbrwm-BsR1Z{L}XhQt=oc~aeejPFXy|FgX+4X}C! zsL?<9yZ8#Caj<~jCcR5?9A;qV%1mSZJ3j|+`|;HS;XI5`V{&hq2;v>}j3YK1?pV?~ zK&6OH@r)y(kR;i`JaREjN%!EdJ*=DF1TTSTPJdsOg`!QwX72W>CA@qA*ond(yRtAw zjziyQ>{(I6CMF5Hrwh1FI)C9<7F@r%;F9Meb#yV%stBEZ069i&^{RVEv*`~|=ieel zyTTCFZadvo;TtP6l-246cU+gT`QLPqI~hnqZAA)d8&?mHwlQpJJdts7^SeWs2>SJY z);~aEZylS_FillRLH-;}F~`t{O8dgsR6@*2pv`G`e9CY^oK^9S^1dK%Vb^+i?vu1L zOQW8Vf-ROQsYkB|57Y4NRp)OiiVbMb79T~-jdP^e8w-=;EFh3c>1dNeTXt~KL>B;vtB zi>uX}N1pbTE6i?JdA9vwL!71YMpkRB!xCwE-!5A84?xg{Nx~*E>DrmS+|k68x;RvP zg?x>~-Gwxl7EYUru#PB5+nU%(i%D9$yX_T`@SVA6{ReRTP&=dbx=Ap^D>T{JuQnL| z0KRbU<)+r@{#6Ofscr$j#Dn|v%+2u(H0N;0t&m`hmRi+4MyQ+i;sY3C&5DgTQ7(BU z8--O;O1NyIC3bj{>xNR-aCDtj)bN88_O_Hl(NK92Ux z?wtGM%x~RC0;zZ_TdnY2~GK zem|<=j_Bylc6FqWQ#@>BxlF&l2yjjxbC>+g@sg}JA;}fF20!z3UAApNp@3Gp!%|!2 zL?)PYs048nWhe_-Te}tbP?U4nl3=DN@_N!9CDkYbgT>QWVYts_`pE^6k$Y`tB0VE! zm$p>Rw_gKp86%A*em)H5yGlGY;Ah6Xo$Wr_t!dq)#$|@?g_`!$U52PQkD1p#n342; zeRfP8_vJ@Tk*TP~Z$FzKpsHRr)!eY?p_yC5`;n}w??=e)M7deqo^Gcsu zA~D~)QaDX1d-F6~fE)kI-TN|GOw|F%>pStMrQ;hSG)((wjQk2eZ2Rr9Oe>l511;AM zDK3E&Hr|BW7GKD>Tg@WV{Ux=EDv^ifYVJ$XKW3lYDt8l)%qIwFA#1ms-CEi`YGt7< zZ3aBQU0%2QhModh%WUQK)NK+zV{bJkx4|LNG<06J(^^7@Vqpl}RfcJfczki4yDvIL z|8o?2D*ODu=br(e-~NAjz?;-+RH|YcpONsHWJTnyIhn}|WlP`mJek%(G#-M)T!VcW ze!ahGor~7~~@NUU@N@RI<&A7|tWV4#~ zf1^EA4rdFC=QI~63>6E=Gx%mTj`mRjv*7zbV&XI5IoGU^$g;&8u~*#J>2JI$m~!HD z+(mE6K_SeMjd3{B5J6-C%l2QHO5TAnwLcGe`kv&y52gV46S7eMNH-(3$HxWYqQA@^ zw=_a01c@%KLxL*z`FE-^jiOKfu&CRlJ?ft(J-$VCyNBOApxyofSf!lD&o2vRg)&g% zVA`$5S*?dv6fJ6hE-iG_>3dZOq`R>!VSTi~9M1Msoa`~V%j;hDvbRNmL&L9`)~Lc^ zEqI2Mk{IKitJ7dR9#nfC4JF}K!NQ6>47pDa6ng}jNc}1Pz<-Bn>o_-=(mIc*e<0_w zyL96@RN_n2(ZT~X}M&#>7pE%cgsW$_;i_GXx2Y;1M+aJgVWM~d7lbEP2) zj7$J;XyyVkZ+WB}SB@5wQVc11mmebH{3%^9PfLTUEI;Cjb>o1+D;e)bfi8Sb<15h|31t(tXZ~fg^NiQacTuHRK^Pg6LcqWw z^ihslS2!vZJK6F$-sni)Fs{`?UYl6)l0nve)eyy z#bFt&e~fI8etgp`BCqYiyfNpcGu#xjdb(Nk!(*~UIxiz$6fa7+WjM9o@Wr$7r*YSJGW zLufuI-AO|iUEgroOK4EP%>*l%@lO+{3rW6yHGK55fX?zY{Xt9MI@U?pk^Q4-_P0k? z40XP^F@*PyBP; z|2`Mam{GUhm#CDAhAd<`b{I>Dq(ZuN5=@hNkn$LMfA`4(f0`0h? z)aS$3kl8SMNobIS0kR?xUR1@uG}m61{i$h2Z;aW-wrGpo#vkX65M6S>Q{ry4RTyVz zR@gVc$4ZR&00FScf73_OjD6#EF>?QZX6SdLwU`aX0}}~|e$_p^F@Q!!kbFLt#rE!k zpiBP&-nwF5GVC7PN-RniHmjarLW*EiRUT^C+ta?Prm#lrHeS)uPc1yyk7ss6Ac%GpN9`^*;vj^v+=&%0( z9FkRGgMToS^`lpg?Y#Er$gi~f4&-Ck8f!NK42*S>-O2sLK;+Ctt9`vh@|#Zb!Q1a2 zfIpD_ehCw{iU)g~9y%-!d*Z_mFG(&I=O*hCF&CIg@?klds~5&|qn2%K58oz|9`cJG za{{rqQ*Wiv45$Hx*$+hyZbb84j^AB7??07R+4$cUgfO45?f!>OTA-+=pUPkG&5{4K zFhj;xR`!_~%UQO*Ad`h;^E2KOc)6T#`DtOL1JB(r@hw^@0y|}?xc?B<)sUi3GXkEi z!r7*Tx&9P%Bpb3>0gFf2D2cf;8pJK!Y`zrC$uB^bq7L+EhwZAfV_VwJ}mUA0TlP^k``tLs{J z*l|wjFRwa^!cf9=#+}Oi?`g4P)_PL7QfcjeJ$^HID0u+S@awjk2K{d+W!NLh5aunUDiW;1v$>52xpKvMM7_MM@;mz;h|}T6!G0-I3%<%Qox>C?ui3%p2ryi7x5 z7Ca|$Mc9wpe}EZiRo0;iO}Sr7m>Schp=otX2vccUO$asTIHjtxR-8d|Q1+yP7?Uqy zwC_6Bt#C!U3{INxDHUPT8gDOK^+ja}s=Tn9bi!aY zHZw5eroIA_pBlj8^wC>S_DYE!$ZfjjE<;mTNH6<5{5i&s{_Rt&r?Pb(L((q^J<1_~B47WQT?H4$~GCE822kctQy20cYo7U2gtF>vxJUE))= z1H6S1bc}cF{$wYKoajWA3sOzjky5zUurq4RSMSf#-aFTMK-RpCp5kCDFFd6e44m8* z8MTM=(@E*M&zYi164!BU*(Wkz0S{B^sEA33X_T*E_&}#9-lbCC5(*%QT>BLB27}{4uFoMYs$}f6%vsl4NPdWgAl`-nH|_} zxq$PB?MgvmgfNrFVGZD%A*H>SF1MpeW%(ypM;`Y!N?-Xxa0Es*X<;>b6 zu7K{d+wcb5V7@zr?gp3qSqY;;N>2RE2~l2(y_%+JrS%aDhC0WJYIcklP}+|#Jc-wl zAaE|_u1La})ARrCMi1T4UoQOWv)_$_=0tzYlm*7Ay?!l3=rUQB-niuCPRUdfv_$nj zIsRJuI-UU`1@kGLN`#cOLr;!T7WT%;Lz zk3dXUA`QTKYOa+{aO%Vc4blqrxdHZbeML3;RGwaE>p_h>-q|)z+UMHgcRpyhAejpi z**i)HYOY$?Y9ar_PQn~W&lQF;WmT=funF@=PgUqYEdTa0=M^Ewao0K8ojd*ncEO&4 z5i!qk7OXhEhZR8BP#@bcZ}`@)2CP4|_fM2Hqrw`=cmE@n-1Dvo>sIISj>NX$O7)`p zF~F>M{~On2UI-dogpNfq^;V)2_Q4MIlx`ZxeFopULag!f%&nV-O-_Iy-4R9(Y#(uSDoKUsbl9#Rg3{qfMQZC zssDsOUp({glu@C%Ev&da~73N z&UjMRYozu%hxWagCDlAS*LpNad5Eq)!a+JTJ*;P7!Tw>goAn54 zEUz-qT5sw@$$p3#DK61OhHK*r%fX`hu8O8919x|SRG%2dpl^Y~dD&AE<%c}VdNqe6 z=bk)Nf@7tNA)&yly6Eyv@0!HSOd>R_l`pvI`QY|3SWy|_PCaA^ZsBTCYC5vZg-t3P zF;2pJ0$Db}W;HH9+rJM1u!JP$cCnt4)YIDhZLw7I9&cVAFMBXTwPsAt1e)IP9qCwd zLeZy;xY{o_`1p{F?@CtGT6Z0S_~PR)q@ZC)lET2<`SA)$OZm$IX(z%_P_5*VYm1X9 zE3C5Bf`#TGMTtHiW7L!DE=5&QRA8_8?BJWz}t7 zc2ZGQ(Zk$|5Ao|K+}N(=;sAXo|ATSA*-xOJ9Q7`X-j(sdnEVCFCr``qF@Um_h{2!e zBEsgGS#b9ZfD9D=1@^#`@O&fmKZ`gT&s-n4!ObdEP<_BLD$AXm#_`f zf93qme9L=zm#)KI&8)uH{smV<1VWBS+={quFwOjw%<=bpClo|$c2&m!_c}*sy;|_C z|7FP~XO>Au7GGQUo~v&5WMDM&RBvGVb9*7<+x?sDS6HzxyLV|;)9)*>`3aY<-P{7) zo)tQM^%N5WyYldWog2+O=UkgUZQ~OYnTPA_Tx-kF3Y2F%IruNdrf)6Zbo-@xa_v&9 zu$UKj7^F_!y)XYe^|}1!?pwkOs^o8+UOp>2>g(a z_+rqND|TG%UO(cVZP>=uDaYLLd%?H3_gGx!TD!1nzE8G&PTbZ#rrzIyAP{pU7TbT!(JpldqTrs;K{DF?VHa3URo9UpTTDB{V%6Kx35~- zRvTC`Z&sSG$kea=z|A9D-x^u8Ge6^anl*vsMhR0z#`6`+SFG$meADaNF5gRA*Bva# zxOJ)MoyWrk4~i;SR){`cdaLW`@>Q>v?hEaZR`uQHCM-OE+R<2^P4X>rXOjEROGw&i z%ghbwdYaqR_OX6a#k0lxoFj!F)v4SU{(H3VaAkIia>84=`VaFFca;{*Dh+l^8pKkcd&lK4p4;i}bFH6a>%}l3$-_Z-> zdcAK!(|?BG)GqUwRXo7Ck;ncYHofOuH?i?}j^4qN#yw0I=a^fc+`5-%YB{_8%D}UQ ztJ}(My_@&gnsx2EdEy=4JN|@CcRMNFW_q@Nv!C1}hQ}vjx6NJ^m3OnWaog$xZEe|S zF2^)1E{+W6T-c=U@#5Q@O`bEg|JYPEMHz@&T$|OoYnnE1&Ua4jJj19n;*v}FKFS)b znzOyQ<3~s5MV_9Ijq?l`)&RG0lQhUh!{yNPI}j|Gx Date: Tue, 25 Jul 2023 18:54:56 +0200 Subject: [PATCH 125/337] fix fixtures state --- tests/fixtures/accounts.py | 74 ++++++++++++++++++++------- tests/fixtures/mocks.py | 2 +- tests/pools/test_exchange_received.py | 31 ++--------- 3 files changed, 62 insertions(+), 45 deletions(-) diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 6b8faa1b..b9bae8e9 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -33,32 +33,32 @@ def alice(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def bob(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def charlie(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def dave(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def erin(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def frank(): return boa.env.generate_address() -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def accounts(bob, charlie, dave, erin, frank): return [bob, charlie, dave, erin, frank] @@ -76,32 +76,32 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): mint_account(owner, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_owner(owner, pool_tokens, swap): approve_account(owner, pool_tokens, swap) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): mint_account(bob, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) @@ -118,7 +118,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal base_pool.add_liquidity(amounts, 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def add_initial_liquidity_owner( owner, approve_owner, @@ -148,7 +148,7 @@ def add_initial_liquidity_owner( swap.add_liquidity([to_mint_token0, lp_token_bal], 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def add_initial_liquidity_alice( alice, approve_alice, @@ -171,7 +171,7 @@ def add_initial_liquidity_alice( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def mint_meta_bob( bob, mint_bob, @@ -187,7 +187,45 @@ def mint_meta_bob( assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def approve_meta_bob(bob, underlying_tokens, swap): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) + with boa.env.prank(bob): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) + + +@pytest.fixture(scope="module") +def initial_setup( + alice, + bob, + approve_alice, + mint_alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_amounts, + underlying_tokens, +): + with boa.env.anchor(): + if pool_type == 0: + with boa.env.prank(alice): + swap.add_liquidity(deposit_amounts, 0) + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(alice): + base_pool_lp_token.approve(swap.address, 2**256 - 1) + swap.add_liquidity(deposit_amounts, 0) + + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) + assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) + + with boa.env.prank(bob): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) + + yield diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py index 2046c031..7555d1cb 100644 --- a/tests/fixtures/mocks.py +++ b/tests/fixtures/mocks.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="function") +@pytest.fixture(scope="module") def callback_contract(bob, swap, pool_tokens, underlying_tokens): with boa.env.prank(bob): diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 4c223016..c51b05d2 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -7,6 +7,7 @@ from tests.utils.tokens import mint_for_testing SWAP_AMOUNT = INITIAL_AMOUNT // 1000 +pytestmark = pytest.mark.usefixtures("initial_setup") @pytest.fixture(scope="function") @@ -17,7 +18,6 @@ def transfer_and_swap( underlying_tokens, pool_type, base_pool, - mint_meta_bob, base_pool_lp_token, base_pool_tokens, base_pool_decimals, @@ -111,13 +111,8 @@ def test_exchange_received_nonrebasing( bob, swap, pool_tokens, - mint_bob, - mint_meta_bob, - approve_bob, - approve_meta_bob, sending, receiving, - add_initial_liquidity_owner, transfer_and_swap, ): swap_data = transfer_and_swap(swap, sending, receiving, False) @@ -131,20 +126,14 @@ def test_exchange_received_nonrebasing( @pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_not_received( - bob, swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner -): - +def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving): with boa.env.prank(bob), boa.reverts(): swap.exchange_received(sending, receiving, 1, 0, bob) @pytest.mark.only_for_token_types(2) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_received_rebasing_reverts( - bob, swap, transfer_and_swap, pool_tokens, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner -): - +def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) @@ -157,13 +146,9 @@ def test_exchange_underlying_received_nonrebasing( swap, transfer_and_swap, underlying_tokens, - mint_bob, - approve_bob, sending, receiving, - add_initial_liquidity_owner, ): - swap_data = transfer_and_swap(swap, sending, receiving, True) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] @@ -189,10 +174,7 @@ def test_exchange_underlying_received_nonrebasing( @pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_not_received( - bob, swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner -): - +def test_exchange_underlying_not_received(bob, swap, sending, receiving): with boa.env.prank(bob), boa.reverts(): swap.exchange_underlying_received(sending, receiving, 1, 0, bob) @@ -200,10 +182,7 @@ def test_exchange_underlying_not_received( @pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.only_for_token_types(2) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_received_rebasing_reverts( - swap, transfer_and_swap, mint_bob, approve_bob, sending, receiving, add_initial_liquidity_owner -): - +def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): if sending == 0: with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) From 04133c71539aecd3f45633fd34fdaa7bf1ce6bc3 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:41:53 +0200 Subject: [PATCH 126/337] fix: initial setup fixture --- tests/fixtures/accounts.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index b9bae8e9..6547086f 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -1,3 +1,5 @@ +import math + import boa import pytest from boa.environment import AddressType @@ -216,13 +218,24 @@ def initial_setup( swap.add_liquidity(deposit_amounts, 0) else: add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + alice_bp_balance_norm = base_pool_lp_token.balanceOf(alice) / 10**18 + alice_mp_balance_norm = underlying_tokens[0].balanceOf(alice) / 10 ** underlying_tokens[0].decimals() + + if alice_mp_balance_norm < alice_bp_balance_norm: + mint_for_testing( + alice, + int(math.ceil(alice_bp_balance_norm) * 10 ** underlying_tokens[0].decimals()), + underlying_tokens[0], + ) + with boa.env.prank(alice): + underlying_tokens[0].approve(swap.address, 2**256 - 1) base_pool_lp_token.approve(swap.address, 2**256 - 1) swap.add_liquidity(deposit_amounts, 0) add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) - assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) + assert underlying_tokens[0].balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) with boa.env.prank(bob): for token in underlying_tokens: From f1781418a1b35eae2e3c87d3ab8b4753e66e4b93 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:47:39 +0200 Subject: [PATCH 127/337] add offpeg fee params to deployment method --- contracts/main/CurveStableSwapFactoryNG.vy | 4 ++++ tests/fixtures/pools.py | 14 +++++++++++++- tests/test_factory.py | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 7fbcbab5..9ee64b20 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -453,6 +453,7 @@ def deploy_plain_pool( _coins: DynArray[address, MAX_COINS], _A: uint256, _fee: uint256, + _offpeg_fee_multiplier: uint256, _ma_exp_time: uint256, _implementation_idx: uint256, _asset_types: DynArray[uint8, MAX_COINS], @@ -519,6 +520,7 @@ def deploy_plain_pool( _symbol, # _symbol: String[10] _A, # _A: uint256 _fee, # _fee: uint256 + _offpeg_fee_multiplier, # _offpeg_fee_multiplier: uint256 _ma_exp_time, # _ma_exp_time: uint256 _coins, # _coins: DynArray[address, MAX_COINS] _rate_multipliers, # _rate_multipliers: DynArray[uint256, MAX_COINS] @@ -572,6 +574,7 @@ def deploy_metapool( _coin: address, _A: uint256, _fee: uint256, + _offpeg_fee_multiplier: uint256, _ma_exp_time: uint256, _implementation_idx: uint256, _asset_type: uint8, @@ -642,6 +645,7 @@ def deploy_metapool( _symbol, # _symbol: String[10] _A, # _A: uint256 _fee, # _fee: uint256 + _offpeg_fee_multiplier, # _offpeg_fee_multiplier: uint256 _ma_exp_time, # _ma_exp_time: uint256 self.math_implementation, # _math_implementation: address _base_pool, # _base_pool: address diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 0939f473..9f239bbc 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -22,6 +22,7 @@ def swap( set_metapool_implementations, ): oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") + offpeg_fee_multiplier = 20000000000 if pool_type == 0: A = 2000 fee = 1000000 @@ -50,7 +51,17 @@ def swap( with boa.env.prank(deployer): pool = factory.deploy_plain_pool( - "test", "test", [t.address for t in pool_tokens], A, fee, 866, 0, asset_types, method_ids, oracles + "test", + "test", + [t.address for t in pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, ) return amm_interface.at(pool) @@ -85,6 +96,7 @@ def swap( underlying_tokens[0].address, # _coin: address, A, # _A: uint256, fee, # _fee: uint256, + offpeg_fee_multiplier, 866, # _ma_exp_time: uint256, 0, # _implementation_idx: uint256 metapool_token_type, # _asset_type: uint8 diff --git a/tests/test_factory.py b/tests/test_factory.py index 4ba32cb8..9ff9195f 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -220,6 +220,7 @@ def test_deploy_plain_pool( [t.address for t in pool_tokens], 2000, 1000000, + 20000000000, 866, 0, [0] * pool_size, @@ -258,6 +259,7 @@ def test_pool_count( [t.address for t in pool_tokens], 2000, 1000000, + 20000000000, 866, 0, [0] * pool_size, From 8b332ecdcc57c4735555725be31a41dc12d316bf Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:28:21 +0200 Subject: [PATCH 128/337] some cosmetic docstring changes --- contracts/main/CurveStableSwapMetaNG.vy | 3 ++- contracts/main/CurveStableSwapNG.vy | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 0748dd84..1580613e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -25,7 +25,7 @@ 2. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 3. Adds feature: `exchange_received`: swaps that expect an ERC20 transfer to have occurred + 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. @@ -35,6 +35,7 @@ 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index aa0bada4..4df4c021 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -17,26 +17,23 @@ 2. ERC20 tokens can have arbitrary decimals (<=18). 3. ERC20 tokens that rebase (either positive or fee on transfer) 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) - Additional features include: - 1. Support for rebasing tokens: but this disables - `exchange_received`. - 2. Support for ERC20 tokens with rate oracles (e.g. wstETH, sDAI) Note: Oracle precision _must_ be 10**18. - 3. Adds oracles based on AMM State Price (and _not_ last traded price). - 4. Adds exchanging tokens with callbacks that allows for: + Additional features include: + 1. Adds oracles based on AMM State Price (and _not_ last traded price). + 2. Adds exchanging tokens with callbacks that allows for: a. reduced ERC20 token transfers in zap contracts b. swaps without transferFrom (no need for token approvals) - 5. Adds feature: `exchange_received`, which is inspired - by Uniswap V2: swaps that expect an ERC20 transfer to have occurred + 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 6. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 From 03cab283db8eb6c24480bd4e0aaecd6299c02955 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:08:47 +0200 Subject: [PATCH 129/337] add docstrings for constructor; add limits for offpeg fee multiplier in factory --- contracts/main/CurveStableSwapFactoryNG.vy | 8 +++++++- contracts/main/CurveStableSwapMetaNG.vy | 10 +++++++++- contracts/main/CurveStableSwapNG.vy | 5 ++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 9ee64b20..b1d3cae7 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -75,6 +75,9 @@ event LiquidityGaugeDeployed: MAX_COINS: constant(uint256) = 8 ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 + admin: public(address) future_admin: public(address) @@ -486,10 +489,11 @@ def deploy_plain_pool( @param _oracles Array of rate oracle addresses. @return Address of the deployed pool """ - assert _fee <= 100000000, "Invalid fee" assert len(_coins) == len(_method_ids), "All coin arrays should be same length" assert len(_coins) == len(_oracles), "All coin arrays should be same length" assert len(_coins) == len(_asset_types), "All coin arrays should be same length" + assert _fee <= 100000000, "Invalid fee" + assert _offpeg_fee_multiplier * _fee <= MAX_FEE * FEE_DENOMINATOR n_coins: uint256 = len(_coins) _rate_multipliers: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -614,6 +618,8 @@ def deploy_metapool( """ assert not self.base_pool_assets[_coin], "Invalid asset: Cannot pair base pool asset with base pool's LP token" assert _fee <= 100000000, "Invalid fee" + assert _offpeg_fee_multiplier * _fee <= MAX_FEE * FEE_DENOMINATOR + base_pool_n_coins: uint256 = len(self.base_pool_data[_base_pool].coins) assert base_pool_n_coins != 0, "Base pool is not added" diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 1580613e..c9d87dfd 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -271,7 +271,6 @@ def __init__( @notice Initialize the pool contract @param _name Name of the new plain pool. @param _symbol Symbol for the new plain pool. - @param _coins List of addresses of the coins being used in the pool. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. Suggested values include: @@ -281,8 +280,17 @@ def __init__( @param _fee Trade fee, given as an integer with 1e10 precision. The the maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. + @param _offpeg_fee_multiplier A multiplier that determines how much to increase + Fees by when assets in the AMM depeg. Example: 20000000000 @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _math_implementation Contract containing Math methods + @param _base_pool The underlying AMM of the LP token _coins[0] is paired against + @param _coins List of addresses of the coins being used in the pool. For metapool this is + the coin (say LUSD) vs (say) 3crv as: [LUSD, 3CRV]. Length is always 2. + @params _base_coins coins in the underlying base pool. + @params _rate_multipliers Rate multipliers of the individual coins. For Metapools it is: + [10 ** (36 - _coins[0].decimals()), 10 ** 18]. @param _asset_types Array of uint8 representing tokens in pool @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 4df4c021..d315cade 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -223,7 +223,6 @@ def __init__( @notice Initialize the pool contract @param _name Name of the new plain pool. @param _symbol Symbol for the new plain pool. - @param _coins List of addresses of the coins being used in the pool. @param _A Amplification co-efficient - a lower value here means less tolerance for imbalance within the pool's assets. Suggested values include: @@ -233,8 +232,12 @@ def __init__( @param _fee Trade fee, given as an integer with 1e10 precision. The the maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. + @param _offpeg_fee_multiplier A multiplier that determines how much to increase + Fees by when assets in the AMM depeg. Example value: 20000000000 @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _coins List of addresses of the coins being used in the pool. + @param _rate_multipliers An array of: [10 ** (36 - _coins[n].decimals()), ... for n in range(N_COINS)] @param _asset_types Array of uint8 representing tokens in pool @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. From 292a25e828655e046ede98693aeb6ea1409e4959 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:52:09 +0200 Subject: [PATCH 130/337] add get_dx with dynamic fee --- contracts/main/CurveStableSwapNGViews.vy | 8 +++++-- tests/pools/test_swap_getters.py | 27 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/pools/test_swap_getters.py diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 3fef29b3..85dc20d5 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -58,8 +58,12 @@ def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: amp: uint256 = StableSwapNG(pool).A() * A_PRECISION D: uint256 = self.get_D(xp, amp, N_COINS) - # TODO: Add Dynamic fee - y: uint256 = xp[j] - (dy * rates[j] / PRECISION + 1) * FEE_DENOMINATOR / (FEE_DENOMINATOR - StableSwapNG(pool).fee()) + base_fee: uint256 = StableSwapNG(pool).fee() + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + dy_with_fee: uint256 = dy * rates[j] / PRECISION + 1 + dynamic_fee: uint256 = self._dynamic_fee(xp[i], xp[j], base_fee, fee_multiplier) + + y: uint256 = xp[j] - dy_with_fee * FEE_DENOMINATOR / (FEE_DENOMINATOR - dynamic_fee) x: uint256 = self.get_y(j, i, y, xp, amp, D, N_COINS) return (x - xp[i]) * PRECISION / rates[i] diff --git a/tests/pools/test_swap_getters.py b/tests/pools/test_swap_getters.py new file mode 100644 index 00000000..e525c567 --- /dev/null +++ b/tests/pools/test_swap_getters.py @@ -0,0 +1,27 @@ +import pytest +from boa.test import strategy +from hypothesis import given, settings + +SETTINGS = {"max_examples": 100, "deadline": None} + + +@given( + amount_in=strategy("decimal", min_value=0.001, max_value=10**6), + i=strategy("uint", min_value=0, max_value=2), + j=strategy("uint", min_value=0, max_value=2), +) +@settings(**SETTINGS) +def test_get_dx(i, j, amount_in, swap, factory, initial_setup): + + n_coins = swap.N_COINS() + if i == j or max(i, j) >= n_coins: + return + + _token_i_precision = 10 ** factory.get_decimals(swap)[i] + _amount_in = int(amount_in * _token_i_precision) + + expected_out = swap.get_dy(i, j, _amount_in) + approx_in = swap.get_dx(i, j, expected_out) + + # not accurate, but close enough: + assert _amount_in == pytest.approx(approx_in, 1e-2) From 7e0839ef7c08dfc92c7647cd91c9ca6679032ff6 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:22:44 +0200 Subject: [PATCH 131/337] tests work --- .github/workflows/test_pools_2.yaml | 4 +- contracts/mocks/ERC20Rebasing.vy | 9 -- tests/fixtures/accounts.py | 22 +++- tests/pools/basic/test_liquidity.py | 169 ++++++++++++++++++---------- 4 files changed, 129 insertions(+), 75 deletions(-) diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml index a50fedd2..a07f0827 100644 --- a/.github/workflows/test_pools_2.yaml +++ b/.github/workflows/test_pools_2.yaml @@ -33,11 +33,9 @@ jobs: - name: Run All Token Tests 18,18 run: | source .venv/bin/activate - pytest tests/pools/ --pool-size=2 --pool-type=basic --decimals=18,18 -n auto + pytest tests/pools/ --pool-size=2 -n auto - name: Run Plain Tests 18,6 run: | source .venv/bin/activate pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto - -# TODO: add meta diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index b41576f7..73b3507d 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -13,12 +13,6 @@ event Transfer: _value: uint256 -event TransferShares: - _from: indexed(address) - _to: indexed(address) - _value: uint256 - - event Approval: _owner: indexed(address) _spender: indexed(address) @@ -75,7 +69,6 @@ def transfer(_to: address, _value: uint256) -> bool: self.shares[msg.sender] -= _shares self.shares[_to] += _shares log Transfer(msg.sender, _to, _value) - log TransferShares(msg.sender, _to, _shares) return True @@ -92,7 +85,6 @@ def transferFrom(_from: address, _to: address, _value: uint256) -> bool: self.allowances[_from][msg.sender] -= _new_value log Transfer(_from, _to, _new_value) - log TransferShares(_from, _to, _new_value) return True @@ -154,6 +146,5 @@ def _mint_for_testing(_target: address, _value: uint256) -> bool: self.shares[_target] += _shares log Transfer(empty(address), _target, _value) - log TransferShares(empty(address), _target, _shares) return True diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 6547086f..45c8aa1c 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -209,13 +209,23 @@ def initial_setup( base_pool_tokens, base_pool_decimals, base_pool_lp_token, + initial_balance, initial_amounts, + pool_tokens, underlying_tokens, ): with boa.env.anchor(): + mint_for_testing(bob, 1 * 10**18, None, True) + if pool_type == 0: with boa.env.prank(alice): swap.add_liquidity(deposit_amounts, 0) + + mint_account(bob, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(bob): + for token in pool_tokens: + token.approve(swap.address, 2**256 - 1) + else: add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) alice_bp_balance_norm = base_pool_lp_token.balanceOf(alice) / 10**18 @@ -233,12 +243,12 @@ def initial_setup( base_pool_lp_token.approve(swap.address, 2**256 - 1) swap.add_liquidity(deposit_amounts, 0) - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) - assert underlying_tokens[0].balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) + assert underlying_tokens[0].balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) - with boa.env.prank(bob): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) + with boa.env.prank(bob): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) yield diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index f2e426fd..29a2d716 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -1,102 +1,157 @@ import boa import pytest +from tests.fixtures.constants import INITIAL_AMOUNT from tests.utils.transactions import call_returning_result_and_logs -DEPOSIT_AMOUNT = 500_000 +pytestmark = pytest.mark.usefixtures("initial_setup") class TestLiquidityMethods: - @pytest.mark.usefixtures("add_initial_liquidity_alice", "mint_bob", "approve_bob") class TestAddLiquidity: - @pytest.mark.parametrize("use_eth", (True, False), scope="module") def test_add_liquidity( self, bob, swap, - is_eth_pool, + pool_type, pool_tokens, + underlying_tokens, deposit_amounts, - initial_balance, initial_amounts, - use_eth, pool_token_types, + metapool_token_type, ): - value = deposit_amounts[0] if (is_eth_pool and use_eth) else 0 - swap.add_liquidity(deposit_amounts, 0, use_eth, sender=bob, value=value) + swap.add_liquidity(deposit_amounts, 0, sender=bob) is_ideal = True - for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): - if pool_token_types[i] == 0 or pool_token_types[i] == 2 or (pool_tokens[i] == 1 and not use_eth): - assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 - + if pool_type == 0: + for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): if pool_token_types[i] == 2: is_ideal = False + if i == 0: # up rebasing + assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 + else: # down rebasing + assert pool_token.balanceOf(bob) <= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] * 2 + else: + assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + + if pool_token_types[i] == 1: + is_ideal = False + + ideal = len(pool_tokens) * INITIAL_AMOUNT // 2 * 10**18 + if is_ideal: + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 + else: + if metapool_token_type == 2: + assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - deposit_amounts[0] + assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] * 2 + else: + assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - deposit_amounts[0] + assert underlying_tokens[0].balanceOf(swap.address) == deposit_amounts[0] * 2 - elif pool_token_types[i] == 1 and use_eth: - assert boa.env.get_balance(bob) == initial_balance - value - assert pool_token.balanceOf(bob) == initial_amounts[i] - assert boa.env.get_balance(swap.address) == 0 - assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + if metapool_token_type == 0: + ideal = INITIAL_AMOUNT * 10**18 # // 2 * 2 + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 - elif pool_token_types[i] == 3: - is_ideal = False - if i == 0: # up rebasing - assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 - else: # down rebasing - assert pool_token.balanceOf(bob) <= initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] * 2 - - ideal = len(pool_tokens) * DEPOSIT_AMOUNT * 10**18 - if is_ideal: - assert abs(swap.balanceOf(bob) - ideal) <= 1 - assert abs(swap.totalSupply() - ideal * 2) <= 2 + assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - deposit_amounts[1] + assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] * 2 @pytest.mark.parametrize("idx", (0, 1)) - @pytest.mark.parametrize("use_eth", (True, False)) def test_add_one_coin( - self, bob, swap, pool_tokens, is_eth_pool, deposit_amounts, initial_amounts, idx, weth, use_eth + self, + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + deposit_amounts, + initial_amounts, + pool_token_types, + metapool_token_type, + idx, ): amounts = [0] * len(pool_tokens) amounts[idx] = deposit_amounts[idx] swap.add_liquidity(amounts, 0, sender=bob) + is_ideal = True - for i, pool_token in enumerate(pool_tokens): - if pool_token == weth: - assert boa.env.get_balance(bob) == initial_amounts[i] - amounts[i] - assert boa.env.get_balance(swap) == deposit_amounts[i] + amounts[i] + if pool_type == 0: + for i, pool_token in enumerate(pool_tokens): + if pool_token_types[i] == 2: + is_ideal = False + if i == 0: # up rebasing + assert pool_token.balanceOf(bob) >= initial_amounts[i] - amounts[i] - 1 + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] + amounts[i] - 1 + else: # down rebasing + assert pool_token.balanceOf(bob) <= initial_amounts[i] - amounts[i] + 1 + assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] + amounts[i] + 1 + else: + assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] + amounts[i] + else: + if metapool_token_type == 2: + is_ideal = False + assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - amounts[0] - 1 + assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] + amounts[0] - 1 else: - assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] - assert pool_token.balanceOf(swap) == deposit_amounts[i] + amounts[i] + assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - amounts[0] + assert underlying_tokens[0].balanceOf(swap) == deposit_amounts[0] + amounts[0] - difference = abs(swap.balanceOf(bob) - (10**18 * DEPOSIT_AMOUNT)) - assert difference / (10**18 * DEPOSIT_AMOUNT) < 0.01 + assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - amounts[1] + assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] + amounts[1] - def test_insufficient_balance(self, charlie, swap, decimals): - amounts = [(10**i) for i in decimals] + difference = abs(swap.balanceOf(bob) - deposit_amounts[idx]) + if is_ideal: + assert difference / (deposit_amounts[idx]) < 0.01 + else: + assert difference / (deposit_amounts[idx]) < 0.02 + + def test_insufficient_balance(self, charlie, swap, pool_type, decimals, meta_decimals): + if pool_type == 0: + amounts = [(10**i) for i in decimals] + else: + amounts = [(10**i) for i in [meta_decimals, 18]] with boa.reverts(): # invalid approval or balance swap.add_liquidity(amounts, 0, sender=charlie) - def test_min_amount_too_high(self, bob, swap, deposit_amounts, pool_size): + def test_min_amount_too_high(self, bob, swap, pool_type, deposit_amounts, pool_tokens): + size = 2 + if pool_type == 0: + size = len(pool_tokens) + with boa.reverts(): - swap.add_liquidity(deposit_amounts, pool_size * DEPOSIT_AMOUNT * 10**18 + 1, sender=bob) + swap.add_liquidity(deposit_amounts, size * INITIAL_AMOUNT // 2 * 10**18 * 101 // 100, sender=bob) + + def test_event(self, bob, swap, pool_type, deposit_amounts, pool_tokens, pool_token_types, metapool_token_type): + size = 2 + check_invariant = True + if pool_type == 0: + size = len(pool_tokens) + + for t in pool_token_types: + if t != 0: + check_invariant = False + + if pool_type == 1: + if metapool_token_type != 0: + check_invariant = False - def test_event(self, bob, swap, deposit_amounts): _, events = call_returning_result_and_logs(swap, "add_liquidity", deposit_amounts, 0, sender=bob) assert len(events) == 4 # Transfer token1, Transfer token2, Transfer LP, Add liquidity - assert ( - repr(events[3]) == f"AddLiquidity(provider={bob}, token_amounts={deposit_amounts}, fees=[0, 0], " - f"invariant={2_000_000 * 10 ** 18}, token_supply={swap.totalSupply()})" - ) - - # TODO: fix this - # - # def test_send_eth(self, bob, swap, deposit_amounts, use_eth): - # - # # with boa.reverts(): - # swap.add_liquidity(deposit_amounts, 0, True, sender=bob, value=1) + if check_invariant: + assert ( + repr(events[3]) == f"AddLiquidity(provider={bob}, token_amounts={deposit_amounts}, fees=[0, 0], " + f"invariant={size * INITIAL_AMOUNT * 10**18}, token_supply={swap.totalSupply()})" + ) + + def test_send_eth(self, bob, swap, deposit_amounts): + with boa.reverts(): + swap.add_liquidity(deposit_amounts, 0, sender=bob, value=1) From 8fece9f06f4754006e9d91631115c81bc1d02a20 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:14:43 +0200 Subject: [PATCH 132/337] add get_dx_underlying --- contracts/main/CurveStableSwapNG.vy | 1 + contracts/main/CurveStableSwapNGViews.vy | 134 ++++++++++++++++++----- tests/pools/test_swap_getters.py | 32 +++++- 3 files changed, 138 insertions(+), 29 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index d315cade..37b8f0d4 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -264,6 +264,7 @@ def __init__( self.initial_A = A self.future_A = A self.fee = _fee + self.offpeg_fee_multiplier = _offpeg_fee_multiplier assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 85dc20d5..8095df31 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -49,23 +49,7 @@ def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: @return Amount of `i` predicted """ N_COINS: uint256 = StableSwapNG(pool).N_COINS() - - rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) - - amp: uint256 = StableSwapNG(pool).A() * A_PRECISION - D: uint256 = self.get_D(xp, amp, N_COINS) - - base_fee: uint256 = StableSwapNG(pool).fee() - fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() - dy_with_fee: uint256 = dy * rates[j] / PRECISION + 1 - dynamic_fee: uint256 = self._dynamic_fee(xp[i], xp[j], base_fee, fee_multiplier) - - y: uint256 = xp[j] - dy_with_fee * FEE_DENOMINATOR / (FEE_DENOMINATOR - dynamic_fee) - x: uint256 = self.get_y(j, i, y, xp, amp, D, N_COINS) - return (x - xp[i]) * PRECISION / rates[i] + return self._get_dx(i, j, dy, pool, False, N_COINS) @view @@ -105,12 +89,47 @@ def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: def get_dx_underlying( i: int128, j: int128, - dx: uint256, + dy: uint256, pool: address, ) -> uint256: - # TODO: Add get_dx_underlying - # TODO: Add dynamic fee - return 0 + + BASE_POOL: address = StableSwapNG(pool).BASE_POOL() + BASE_N_COINS: uint256 = StableSwapNG(pool).BASE_N_COINS() + N_COINS: uint256 = StableSwapNG(pool).N_COINS() + base_pool_has_static_fee: bool = self._has_static_fee(BASE_POOL) + + # CASE 1: Swap does not involve Metapool at all. In this case, we kindly as the user + # to use the right pool for their swaps. + if min(i, j) > 0: + raise "Not a Metapool Swap. Use Base pool." + + meta_v_price: uint256 = StableSwapNG(pool).stored_rates(1) + + # CASE 2: + # 1. meta token_0 of (unknown amount) > base pool lp_token + # 2. base pool lp_token > calc_withdraw_one_coin gives dy amount of (j-1)th base coin + # So, need to do the following calculations: + # 1. calc_token_amounts on base pool for depositing liquidity on (j-1)th token > lp_tokens. + # 2. get_dx on metapool for i = 0, and j = 1 (base lp token) with amt calculated in (1). + if i == 0: + # Calculate LP tokens that are burnt to receive dy amount of base_j tokens. + lp_amount_burnt: uint256 = self._base_calc_token_amounts( + dy, j - 1, meta_v_price, BASE_N_COINS, BASE_POOL, False + ) + return self._get_dx(0, 1, lp_amount_burnt, pool, False, N_COINS) + + # CASE 3: Swap in token i-1 from base pool and swap out dy amount of token 0 (j) from metapool. + # 1. deposit i-1 token from base pool > receive base pool lp_token + # 2. swap base pool lp token > 0th token of the metapool + # So, need to do the following calculations: + # 1. get_dx on metapool with i = 0, j = 1 > gives how many base lp tokens are required for receiving + # dy amounts of i-1 tokens from the metapool + # 2. We have number of lp tokens: how many i-1 base pool coins are needed to mint that many tokens? + # We don't have a method where user inputs lp tokens and it gives number of coins of (i-1)th token + # is needed to mint that many base_lp_tokens. Instead, we will use calc_withdraw_one_coin. That's + # close enough. + lp_amount_required: uint256 = self._get_dx(1, 0, dy, pool, False, N_COINS) + return StableSwapNG(BASE_POOL).calc_withdraw_one_coin(lp_amount_required, i-1) @view @@ -158,8 +177,8 @@ def get_dy_underlying( if j == 0: # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() - x = self._base_calc_token_amounts_deposit( - dx, base_i, rates[1], base_n_coins, BASE_POOL + x = self._base_calc_token_amounts( + dx, base_i, rates[1], base_n_coins, BASE_POOL, True ) # Accounting for deposit/withdraw fees approximately x -= x * StableSwapNG(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) @@ -178,7 +197,8 @@ def get_dy_underlying( # calculate output after subtracting dynamic fee base_fee: uint256 = StableSwapNG(pool).fee() fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() - dynamic_fee: uint256 = self._dynamic_fee((xp[i] + x) / 2, (xp[j] + y) / 2, base_fee, fee_multiplier) + + dynamic_fee: uint256 = self._dynamic_fee((xp[meta_i] + x) / 2, (xp[meta_j] + y) / 2, base_fee, fee_multiplier) dy = (dy - dynamic_fee * dy / FEE_DENOMINATOR) # If output is going via the metapool @@ -357,6 +377,59 @@ def dynamic_fee(i: int128, j: int128, pool:address) -> uint256: # ----------------------------- Utility Methods ------------------------------ +@view +@internal +def _has_static_fee(pool: address) -> bool: + + success: bool = False + response: Bytes[32] = b"" + success, response = raw_call( + pool, + concat( + method_id("dynamic_fee(int128,int128)"), + convert(1, bytes32), + convert(0, bytes32) + ), + max_outsize=32, + revert_on_failure=False, + is_static_call=True + ) + + return success + + +@view +@internal +def _get_dx( + i: int128, + j: int128, + dy: uint256, + pool: address, + static_fee: bool, + N_COINS: uint256 +) -> uint256: + + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + rates, balances, xp = self._get_rates_balances_xp(pool, N_COINS) + + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + D: uint256 = self.get_D(xp, amp, N_COINS) + + base_fee: uint256 = StableSwapNG(pool).fee() + dy_with_fee: uint256 = dy * rates[j] / PRECISION + 1 + + fee: uint256 = base_fee + if not static_fee: + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + fee = self._dynamic_fee(xp[i], xp[j], base_fee, fee_multiplier) + + y: uint256 = xp[j] - dy_with_fee * FEE_DENOMINATOR / (FEE_DENOMINATOR - fee) + x: uint256 = self.get_y(j, i, y, xp, amp, D, N_COINS) + return (x - xp[i]) * PRECISION / rates[i] + + @view @internal def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uint256) -> uint256: @@ -373,21 +446,26 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin @internal @view -def _base_calc_token_amounts_deposit( - dx: uint256, base_i: int128, meta_vprice: uint256, base_n_coins: uint256, base_pool: address +def _base_calc_token_amounts( + dx: uint256, + base_i: int128, + meta_vprice: uint256, + base_n_coins: uint256, + base_pool: address, + is_deposit: bool ) -> uint256: if base_n_coins == 2: base_inputs: uint256[2] = empty(uint256[2]) base_inputs[base_i] = dx - return StableSwap2(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + return StableSwap2(base_pool).calc_token_amount(base_inputs, is_deposit) * meta_vprice / PRECISION elif base_n_coins == 3: base_inputs: uint256[3] = empty(uint256[3]) base_inputs[base_i] = dx - return StableSwap3(base_pool).calc_token_amount(base_inputs, True) * meta_vprice / PRECISION + return StableSwap3(base_pool).calc_token_amount(base_inputs, is_deposit) * meta_vprice / PRECISION else: diff --git a/tests/pools/test_swap_getters.py b/tests/pools/test_swap_getters.py index e525c567..31ee6188 100644 --- a/tests/pools/test_swap_getters.py +++ b/tests/pools/test_swap_getters.py @@ -2,7 +2,7 @@ from boa.test import strategy from hypothesis import given, settings -SETTINGS = {"max_examples": 100, "deadline": None} +SETTINGS = {"max_examples": 100, "deadline": 1000} @given( @@ -25,3 +25,33 @@ def test_get_dx(i, j, amount_in, swap, factory, initial_setup): # not accurate, but close enough: assert _amount_in == pytest.approx(approx_in, 1e-2) + + +@pytest.mark.only_for_pool_type(1) # only for metapools +@given( + amount_in=strategy("decimal", min_value=0.001, max_value=10**6), + i=strategy("uint", min_value=0, max_value=4), + j=strategy("uint", min_value=0, max_value=4), +) +@settings(**SETTINGS) +def test_get_dx_underlying(i, j, amount_in, swap, factory, initial_setup): + + base_n_coins = swap.BASE_N_COINS() + + if i == j: + return + + # cap max index to base_n_coins + 1 (metapool coin) excluding LP token + if max(i, j) >= base_n_coins + 1: + return + + if min(i, j) > 0: # base pool swap: it reverts in view contract + return + + _token_i_precision = 10 ** factory.get_underlying_decimals(swap)[i] + _amount_in = int(amount_in * _token_i_precision) + expected_out = swap.get_dy_underlying(i, j, _amount_in) + approx_in = swap.get_dx_underlying(i, j, expected_out) + + # not accurate, but close enough: + assert _amount_in == pytest.approx(approx_in, 1e-2) From 43fd62eb180f4b4527d9b4a3094d002ac5a87da4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:23:50 +0200 Subject: [PATCH 133/337] remove _extended methods: --- contracts/main/CurveStableSwapMetaNG.vy | 123 +----------------------- contracts/main/CurveStableSwapNG.vy | 82 +--------------- 2 files changed, 8 insertions(+), 197 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c9d87dfd..8512a7b1 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -22,20 +22,17 @@ 1. Adds oracles based on AMM State Price (and _not_ last traded price). State prices are calculated _after_ liquidity operations, using bonding curve math. - 2. Adds exchanging tokens with callbacks that allows for: - a. reduced ERC20 token transfers in zap contracts - b. swaps without transferFrom (no need for token approvals) - 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred + 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output + 3. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 4. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 @@ -367,28 +364,15 @@ def _transfer_in( coin_basepool_idx: int128, dx: uint256, dy: uint256, - callbacker: address, - callback_sig: bytes32, sender: address, receiver: address, expect_optimistic_transfer: bool, ) -> uint256: """ - @notice Contains all logic to handle ERC20 or native token transfers - @dev The callback sig must have the following args: - sender: address - receiver: address - coin: address - dx: uint256 - dy: uint256 - The `dy` that the pool enforces is actually min_dy. - Callback only occurs for `exchange_extended`. - @dev If callback_sig is empty, `_transfer_in` does a transferFrom. + @notice Contains all logic to handle ERC20 token transfers. @params _coin address of the coin to transfer in. @params dx amount of `_coin` to transfer into the pool. @params dy amount of `_coin` to transfer out of the pool. - @params callbacker address to call `callback_sig` on. - @params callback_sig signature of the callback function. @params sender address to transfer `_coin` from. @params receiver address to transfer `_coin` to. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer @@ -412,18 +396,6 @@ def _transfer_in( assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported _dx -= _stored_balance # <--- for base_pool coins, stored balance is 0. - elif callback_sig != empty(bytes32): - - raw_call( - callbacker, - concat( - slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, _input_coin.address, dx, dy) - ) - ) - - _dx = _input_coin.balanceOf(self) - _dx - else: assert _input_coin.transferFrom(sender, self, dx, default_return_value=True) @@ -550,44 +522,6 @@ def exchange( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _sender: address, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two coins after a callback - @dev Index values can be found via the `coins` public getter method - Users of this method are dex aggregators,arbitrageurs, or other - users who do not wish to grant approvals to the contract. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: No callback specified - return self._exchange( - _sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, # <---------------------------- callbacker is msg.sender. - _cb, False ) @@ -622,8 +556,6 @@ def exchange_received( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), True, # <--------------------------------------- swap optimistically. ) @@ -653,41 +585,6 @@ def exchange_underlying( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_underlying_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: no callback specified - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, - _cb, False ) @@ -717,8 +614,6 @@ def exchange_underlying_received( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), True ) @@ -758,8 +653,6 @@ def add_liquidity( -1, # <--- we're not handling underlying coins here _amounts[i], 0, - empty(address), - empty(bytes32), msg.sender, empty(address), False, # expect_optimistic_transfer @@ -1058,8 +951,6 @@ def _exchange( _dx: uint256, _min_dy: uint256, receiver: address, - callbacker: address, - callback_sig: bytes32, expect_optimistic_transfer: bool ) -> uint256: @@ -1078,8 +969,6 @@ def _exchange( -1, # <----- we're not handling underlying coins here. _dx, _min_dy, - callbacker, - callback_sig, sender, receiver, expect_optimistic_transfer @@ -1111,8 +1000,6 @@ def _exchange_underlying( _dx: uint256, _min_dy: uint256, receiver: address, - callbacker: address, - callback_sig: bytes32, expect_optimistic_transfer: bool = False ) -> uint256: @@ -1153,8 +1040,6 @@ def _exchange_underlying( base_i, _dx, _min_dy, - callbacker, - callback_sig, sender, receiver, expect_optimistic_transfer, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 37b8f0d4..26439c68 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -20,20 +20,17 @@ Note: Oracle precision _must_ be 10**18. Additional features include: 1. Adds oracles based on AMM State Price (and _not_ last traded price). - 2. Adds exchanging tokens with callbacks that allows for: - a. reduced ERC20 token transfers in zap contracts - b. swaps without transferFrom (no need for token approvals) - 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred + 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 3. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 4. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 @@ -314,29 +311,16 @@ def _transfer_in( coin_idx: int128, dx: uint256, dy: uint256, - callbacker: address, - callback_sig: bytes32, sender: address, receiver: address, expect_optimistic_transfer: bool, ) -> uint256: """ - @notice Contains all logic to handle ERC20 or native token transfers - @dev The callback sig must have the following args: - sender: address - receiver: address - coin: address - dx: uint256 - dy: uint256 - The `dy` that the pool enforces is actually min_dy. - Callback only occurs for `exchange_extended`. - @dev If callback_sig is empty, `_transfer_in` does a transferFrom. + @notice Contains all logic to handle ERC20 token transfers. @params _coin address of the coin to transfer in. @params dx amount of `_coin` to transfer into the pool. @params dy amount of `_coin` to transfer out of the pool. @params mvalue msg.value if the transfer is ETH, 0 otherwise. - @params callbacker address to call `callback_sig` on. - @params callback_sig signature of the callback function. @params sender address to transfer `_coin` from. @params receiver address to transfer `_coin` to. @params expect_optimistic_transfer True if contract expects an optimistic coin transfer @@ -351,18 +335,6 @@ def _transfer_in( assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] - elif callback_sig != empty(bytes32): - - raw_call( - callbacker, - concat( - slice(callback_sig, 0, 4), - _abi_encode(sender, receiver, coins[coin_idx], dx, dy) - ) - ) - - _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx - else: assert ERC20(coins[coin_idx]).transferFrom( @@ -493,44 +465,6 @@ def exchange( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), - False - ) - - -@external -@nonreentrant('lock') -def exchange_extended( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _sender: address, - _receiver: address, - _cb: bytes32 -) -> uint256: - """ - @notice Perform an exchange between two coins after a callback - @dev Index values can be found via the `coins` public getter method - Not payable (does not accept eth). Users of this method are dex aggregators, - arbitrageurs, or other users who do not wish to grant approvals to the contract. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert _cb != empty(bytes32) # dev: No callback specified - return self._exchange( - _sender, - i, - j, - _dx, - _min_dy, - _receiver, - msg.sender, # <---------------------------- callbacker is msg.sender. - _cb, False ) @@ -565,8 +499,6 @@ def exchange_received( _dx, _min_dy, _receiver, - empty(address), - empty(bytes32), True, # <--------------------------------------- swap optimistically. ) @@ -608,8 +540,6 @@ def add_liquidity( i, _amounts[i], 0, - empty(address), - empty(bytes32), msg.sender, empty(address), False, # expect_optimistic_transfer @@ -908,8 +838,6 @@ def _exchange( _dx: uint256, _min_dy: uint256, receiver: address, - callbacker: address, - callback_sig: bytes32, expect_optimistic_transfer: bool ) -> uint256: @@ -927,8 +855,6 @@ def _exchange( i, _dx, _min_dy, - callbacker, - callback_sig, sender, receiver, expect_optimistic_transfer From f26721e26e110d38da4fe77288d7d86db5f661f2 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:19:43 +0200 Subject: [PATCH 134/337] test initial liquidity --- tests/pools/basic/test_liquidity.py | 85 ++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/basic/test_liquidity.py index 29a2d716..bb459571 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/basic/test_liquidity.py @@ -1,13 +1,14 @@ import boa import pytest +from tests.fixtures.accounts import add_base_pool_liquidity, mint_account from tests.fixtures.constants import INITIAL_AMOUNT +from tests.utils.tokens import mint_for_testing from tests.utils.transactions import call_returning_result_and_logs -pytestmark = pytest.mark.usefixtures("initial_setup") - class TestLiquidityMethods: + @pytest.mark.usefixtures("initial_setup") class TestAddLiquidity: def test_add_liquidity( self, @@ -155,3 +156,83 @@ def test_event(self, bob, swap, pool_type, deposit_amounts, pool_tokens, pool_to def test_send_eth(self, bob, swap, deposit_amounts): with boa.reverts(): swap.add_liquidity(deposit_amounts, 0, sender=bob, value=1) + + class TestInitialLiquidity: + @pytest.fixture(scope="module") + def initial_setup_alice( + self, + alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_balance, + initial_amounts, + pool_tokens, + underlying_tokens, + ): + with boa.env.anchor(): + mint_for_testing(alice, 1 * 10**18, None, True) + + if pool_type == 0: + + mint_account(alice, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in pool_tokens: + token.approve(swap.address, 2**256 - 1) + + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) + + yield + + @pytest.mark.parametrize("min_amount", [0, 10**18]) + def test_initial( + self, + alice, + initial_setup_alice, + swap, + pool_type, + pool_tokens, + pool_token_types, + metapool_token_type, + min_amount, + decimals, + meta_decimals, + initial_amounts, + ): + swap_decimals = decimals if pool_type == 0 else [meta_decimals, 18] + amounts = [10**i for i in swap_decimals] + + swap.add_liquidity( + amounts, + len(pool_tokens) * min_amount, + sender=alice, + ) + + token_types = pool_token_types if pool_type == 0 else [metapool_token_type, 18] + + for coin, amount, initial, pool_token_type in zip(pool_tokens, amounts, initial_amounts, token_types): + if pool_token_type == 0: + assert coin.balanceOf(alice) == initial - amount + assert coin.balanceOf(swap) == amount + + # TODO: boa hangs with it, with added single print it passes + # @pytest.mark.parametrize("idx", (0, 1)) + # def test_initial_liquidity_missing_coin( + # self, alice, initial_setup_alice, swap, pool_type, decimals, meta_decimals, idx + # ): + # swap_decimals = decimals if pool_type == 0 else [meta_decimals, 18] + # amounts = [10**i for i in swap_decimals] + # amounts[idx] = 0 + # + # with boa.reverts(): + # swap.add_liquidity(amounts, 0, sender=alice) From 9f1bf63e15bee475a9731ee5697c322bf208799e Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:37:20 +0200 Subject: [PATCH 135/337] add tests --- contracts/main/CurveStableSwapFactoryNG.vy | 5 +- contracts/main/CurveStableSwapMetaNG.vy | 4 +- contracts/main/CurveStableSwapNG.vy | 5 +- tests/pools/basic/__init__.py | 0 tests/pools/test_exchange.py | 138 +++++++++++++++ tests/pools/test_fees.py | 95 ++++++++++ tests/pools/{basic => }/test_liquidity.py | 194 ++++++++++++++++++++- tests/pools/test_ramp_A.py | 93 ++++++++++ tests/pools/test_virtual_price.py | 62 +++++++ 9 files changed, 586 insertions(+), 10 deletions(-) delete mode 100644 tests/pools/basic/__init__.py create mode 100644 tests/pools/test_exchange.py create mode 100644 tests/pools/test_fees.py rename tests/pools/{basic => }/test_liquidity.py (54%) create mode 100644 tests/pools/test_ramp_A.py create mode 100644 tests/pools/test_virtual_price.py diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index b1d3cae7..a30a6bf2 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -440,7 +440,6 @@ def is_meta(_pool: address) -> bool: def get_pool_asset_types(_pool: address) -> DynArray[uint8, MAX_COINS]: """ @notice Query the asset type of `_pool` - @dev 0 = Plain, 1 = Oracle, 2 = Rebasing @param _pool Pool Address @return Integer indicating the pool asset type """ @@ -482,7 +481,6 @@ def deploy_plain_pool( Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_types Asset types for pool, as an integer - 0 = Plain, 1 = Oracle, 2 = Rebasing @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @@ -609,7 +607,6 @@ def deploy_metapool( Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @param _asset_type Asset type for token, as an integer - 0 = Plain, 1 = Oracle, 2 = Rebasing @param _method_id First four bytes of the Keccak-256 hash of the function signatures of the oracle addresses that gives rate oracles. Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @@ -733,7 +730,7 @@ def add_base_pool( @notice Add a base pool to the registry, which may be used in factory metapools @dev Only callable by admin @param _base_pool Pool address to add - @param _asset_types Asset type for pool, as an integer 0 = Plain, 1 = Oracle, 2 = Rebasing + @param _asset_types Asset type for pool, as an integer """ assert msg.sender == self.admin # dev: admin-only function assert len(self.base_pool_data[_base_pool].coins) == 0 # dev: pool exists diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 8512a7b1..9abd355d 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -42,7 +42,7 @@ implements: ERC20 # ------------------------------- Interfaces --------------------------------- interface Factory: - def get_fee_receiver() -> address: view + def fee_receiver() -> address: view def admin() -> address: view def views_implementation() -> address: view @@ -1117,7 +1117,7 @@ def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: @internal def _withdraw_admin_fees(): - fee_receiver: address = factory.get_fee_receiver() + fee_receiver: address = factory.fee_receiver() assert fee_receiver != empty(address) # dev: fee receiver not set admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 26439c68..1ba254f2 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -40,7 +40,7 @@ implements: ERC20 # ------------------------------- Interfaces --------------------------------- interface Factory: - def get_fee_receiver() -> address: view + def fee_receiver() -> address: view def admin() -> address: view def views_implementation() -> address: view @@ -879,8 +879,7 @@ def _exchange( @internal def _withdraw_admin_fees(): - - fee_receiver: address = factory.get_fee_receiver() + fee_receiver: address = factory.fee_receiver() assert fee_receiver != empty(address) # dev: fee receiver not set admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances diff --git a/tests/pools/basic/__init__.py b/tests/pools/basic/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py new file mode 100644 index 00000000..485077f8 --- /dev/null +++ b/tests/pools/test_exchange.py @@ -0,0 +1,138 @@ +import boa +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +class TestExchange: + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals): + amount = 10 ** decimals[sending] + + min_dy = swap.get_dy(sending, receiving, amount) + swap.exchange(sending, receiving, amount, min_dy - 1, sender=bob) + + if pool_type == 0: + received = pool_tokens[receiving].balanceOf(bob) + else: + received = underlying_tokens[receiving].balanceOf(bob) + assert abs(received - min_dy) <= 1 + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_min_dy_imbalanced( + self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals + ): + + amounts = [10**i for i in decimals] + scaler = amounts.copy() # used to scale token amounts when decimals are different + + amounts[sending] = 0 + amounts[receiving] = amounts[receiving] * 1_000_000 + + swap.add_liquidity(amounts, 0, sender=bob) + + # we need to scale these appropriately for tokens with different decimal values + min_dy_sending = swap.get_dy(sending, receiving, scaler[sending]) / scaler[receiving] + min_dy_receiving = swap.get_dy(receiving, sending, scaler[receiving]) / scaler[sending] + + assert min_dy_sending > min_dy_receiving + + class TestExchangeReverts: + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_insufficient_balance(self, charlie, swap, sending, receiving, decimals): + amount = 10 ** decimals[sending] + with boa.reverts(): + swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_min_dy_too_high(self, bob, swap, sending, receiving, decimals): + amount = 10 ** decimals[sending] + min_dy = swap.get_dy(sending, receiving, amount) + with boa.reverts(): + swap.exchange(sending, receiving, amount, min_dy + 2, sender=bob) + + @pytest.mark.parametrize("idx", range(2)) + def test_same_coin(self, bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, idx, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [-1, -(2**127)]) + def test_i_below_zero(self, bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, 0, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [9, 2**127 - 1]) + def test_i_above_n_coins(self, bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, 0, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [-1, -(2**127)]) + def test_j_below_zero(self, bob, swap, idx): + with boa.reverts(): + swap.exchange(0, idx, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [9, 2**127 - 1]) + def test_j_above_n_coins(self, bob, swap, idx): + with boa.reverts(): + swap.exchange(0, idx, 0, 0, sender=bob) + + def test_nonpayable(self, swap, bob): + with boa.reverts(): + swap.exchange(0, 1, 0, 0, sender=bob) + + class TestReceiver: + def test_add_liquidity(self, bob, charlie, swap, initial_amounts): + swap.add_liquidity(initial_amounts, 0, charlie, sender=bob) + + assert swap.balanceOf(bob) == 0 + assert swap.balanceOf(charlie) > 0 + + def test_exchange(self, bob, charlie, swap, pool_type, pool_tokens, underlying_tokens, decimals): + swap.exchange(1, 0, 10**18, 0, charlie, sender=bob) + if pool_type == 0: + assert pool_tokens[0].balanceOf(charlie) > 0 + assert pool_tokens[0].balanceOf(bob) == 0 + else: + assert underlying_tokens[0].balanceOf(charlie) > 0 + assert underlying_tokens[0].balanceOf(bob) == 0 + + def test_remove_liquidity( + self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, pool_size + ): + initial_amount = swap.balanceOf(bob) + withdraw_amount = initial_amount // 4 + swap.remove_liquidity(withdraw_amount, [0] * pool_size, charlie, sender=bob) + + if pool_type == 0: + for coin, amount in zip(pool_tokens, initial_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == amount + else: + for coin, amount in zip(underlying_tokens[:2], initial_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == amount + + assert swap.balanceOf(bob) == initial_amount - withdraw_amount + assert swap.totalSupply() == initial_amount - withdraw_amount + + def test_remove_imbalanced( + self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts + ): + initial_balance = swap.balanceOf(bob) + amounts = [i // 4 for i in initial_amounts] + swap.remove_liquidity_imbalance(amounts, initial_balance, charlie, sender=bob) + + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(charlie) == amounts[i] + assert coin.balanceOf(swap) == initial_amounts[i] - amounts[i] + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(charlie) == amounts[i] + assert coin.balanceOf(swap) == initial_amounts[i] - amounts[i] + + assert swap.balanceOf(bob) / initial_balance == 0.75 + + def test_remove_one_coin(self, alice, charlie, swap, pool_type, pool_tokens, underlying_tokens): + swap.remove_liquidity_one_coin(10**18, 0, 0, charlie, sender=alice) + + assert swap.balanceOf(charlie) == 0 + assert swap.balanceOf(alice) > 0 diff --git a/tests/pools/test_fees.py b/tests/pools/test_fees.py new file mode 100644 index 00000000..05d174f5 --- /dev/null +++ b/tests/pools/test_fees.py @@ -0,0 +1,95 @@ +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +class TestFees: + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_admin_balances( + self, bob, swap, pool_type, pool_tokens, underlying_tokens, initial_amounts, sending, receiving + ): + for send, recv in [(sending, receiving), (receiving, sending)]: + swap.exchange( + send, + recv, + initial_amounts[send], + 0, + sender=bob, + ) + + for i in (sending, receiving): + if pool_type == 0: + admin_fee = pool_tokens[i].balanceOf(swap) - swap.balances(i) + assert admin_fee > 0 + else: + admin_fee = underlying_tokens[i].balanceOf(swap) - swap.balances(i) + assert admin_fee > 0 + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + def test_withdraw_one_coin( + self, + alice, + bob, + fee_receiver, + swap, + pool_type, + pool_tokens, + underlying_tokens, + sending, + receiving, + initial_amounts, + ): + swap.exchange( + sending, + receiving, + initial_amounts[sending], + 0, + sender=bob, + ) + + admin_balance = swap.admin_balances(receiving) + + assert admin_balance > 0 + assert swap.admin_balances(sending) == 0 + + swap.withdraw_admin_fees(sender=alice) + swap_balance = ( + pool_tokens[receiving].balanceOf(swap) if pool_type == 0 else underlying_tokens[receiving].balanceOf(swap) + ) + assert swap.balances(receiving) == swap_balance + assert ( + admin_balance == pool_tokens[receiving].balanceOf(fee_receiver) + if pool_type == 0 + else underlying_tokens[receiving].balanceOf(fee_receiver) + ) + + def test_no_fees(self, bob, fee_receiver, swap, pool_type, pool_tokens, underlying_tokens): + swap.withdraw_admin_fees(sender=bob) + + if pool_type == 0: + for coin in pool_tokens: + assert coin.balanceOf(fee_receiver) == 0 + else: + for coin in underlying_tokens: + assert coin.balanceOf(fee_receiver) == 0 + + def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying_tokens, fee_receiver): + swap.exchange(1, 0, 10**18, 0, sender=bob) + + fees = [] + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(fee_receiver) == 0 + fees.append(swap.admin_balances(i)) + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(fee_receiver) == 0 + fees.append(swap.admin_balances(i)) + + swap.withdraw_admin_fees({"from": bob}) + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(fee_receiver) == fees[i] + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(fee_receiver) == fees[i] diff --git a/tests/pools/basic/test_liquidity.py b/tests/pools/test_liquidity.py similarity index 54% rename from tests/pools/basic/test_liquidity.py rename to tests/pools/test_liquidity.py index bb459571..865d9e40 100644 --- a/tests/pools/basic/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -7,8 +7,8 @@ from tests.utils.transactions import call_returning_result_and_logs +@pytest.mark.usefixtures("initial_setup") class TestLiquidityMethods: - @pytest.mark.usefixtures("initial_setup") class TestAddLiquidity: def test_add_liquidity( self, @@ -236,3 +236,195 @@ def test_initial( # # with boa.reverts(): # swap.add_liquidity(amounts, 0, sender=alice) + + class TestRemoveLiquidity: + @pytest.mark.parametrize("min_amount", (0, 1)) + def test_remove_liquidity( + self, alice, swap, pool_type, pool_tokens, underlying_tokens, min_amount, deposit_amounts + ): + swap.remove_liquidity(swap.balanceOf(alice), [i * min_amount for i in deposit_amounts], sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for coin, amount in zip(coins, deposit_amounts): + assert coin.balanceOf(alice) == amount + assert coin.balanceOf(swap) == 0 + + assert swap.balanceOf(alice) == 0 + assert swap.totalSupply() == 0 + + def test_remove_partial( + self, alice, swap, pool_type, pool_tokens, underlying_tokens, deposit_amounts, pool_size + ): + initial_amount = swap.balanceOf(alice) + withdraw_amount = initial_amount // 2 + swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for coin, amount in zip(coins, deposit_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(alice) == amount + + assert swap.balanceOf(alice) == initial_amount - withdraw_amount + assert swap.totalSupply() == initial_amount - withdraw_amount + + @pytest.mark.parametrize("idx", range(2)) + def test_below_min_amount(self, alice, swap, initial_amounts, idx): + min_amount = initial_amounts.copy() + min_amount[idx] += 1 + + with boa.reverts(): + swap.remove_liquidity(swap.balanceOf(alice), min_amount, sender=alice) + + def test_amount_exceeds_balance(self, alice, swap, plain_pool_size): + with boa.reverts(): + swap.remove_liquidity(swap.balanceOf(alice) + 1, [0] * plain_pool_size, sender=alice) + + def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens, plain_pool_size): + swap.transfer(bob, 10**18, sender=alice) + _, events = call_returning_result_and_logs( + swap, "remove_liquidity", 10**18, [0] * plain_pool_size, sender=alice + ) + + # coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + # event = events[0] + # TODO: add event + + class TestRemoveLiquidityImbalance: + @pytest.mark.parametrize("divisor", [2, 5, 10]) + def test_remove_balanced( + self, alice, swap, pool_type, pool_tokens, underlying_tokens, divisor, deposit_amounts + ): + initial_balance = swap.balanceOf(alice) + amounts = [i // divisor for i in deposit_amounts] + swap.remove_liquidity_imbalance(amounts, initial_balance, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for i, coin in enumerate(coins): + assert coin.balanceOf(alice) == amounts[i] + assert coin.balanceOf(swap) == deposit_amounts[i] - amounts[i] + + assert swap.balanceOf(alice) / initial_balance == 1 - 1 / divisor + + @pytest.mark.parametrize("idx", range(2)) + def test_remove_one( + self, alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size, idx, deposit_amounts + ): + amounts = [0] * pool_size + amounts[idx] = deposit_amounts[idx] // 2 + + lp_balance = pool_size * 1_000_000 * 10**18 + swap.remove_liquidity_imbalance(amounts, lp_balance, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for i, coin in enumerate(coins): + assert coin.balanceOf(alice) == amounts[i] + assert coin.balanceOf(swap) == deposit_amounts[i] - amounts[i] + + actual_balance = swap.balanceOf(alice) + actual_total_supply = swap.totalSupply() + ideal_balance = (2 * pool_size - 1) * lp_balance / (2 * pool_size) + + assert actual_balance == actual_total_supply + assert ideal_balance * 0.9994 < actual_balance < ideal_balance + + @pytest.mark.parametrize("divisor", [1, 2, 10]) + def test_exceed_max_burn(self, alice, swap, pool_size, divisor, deposit_amounts): + amounts = [i // divisor for i in deposit_amounts] + max_burn = pool_size * 1_000_000 * 10**18 // divisor + + with boa.reverts(): + swap.remove_liquidity_imbalance(amounts, max_burn - 1, sender=alice) + + def test_cannot_remove_zero(self, alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) + + def test_no_totalsupply(self, alice, swap, pool_size): + swap.remove_liquidity(swap.totalSupply(), [0] * pool_size, sender=alice) + with boa.reverts(): + swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) + + def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens, pool_size, deposit_amounts): + swap.transfer(bob, swap.balanceOf(alice), sender=alice) + amounts = [i // 5 for i in deposit_amounts] + max_burn = pool_size * 1_000_000 * 10**18 + + # coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + _, events = call_returning_result_and_logs( + swap, "remove_liquidity_imbalance", amounts, max_burn, sender=bob + ) + + # event = events[0] + # TODO: add test event + + class TestRemoveLiquidityOneCoin: + @pytest.mark.parametrize("idx", range(2)) + def test_amount_received(self, alice, swap, pool_type, pool_tokens, underlying_tokens, decimals, idx): + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + decimals = decimals[idx] + wrapped = coins[idx] + + swap.remove_liquidity_one_coin(10**18, idx, 0, sender=alice) + + ideal = 10**decimals + + assert ideal * 0.99 <= wrapped.balanceOf(alice) <= ideal + + @pytest.mark.parametrize("idx", range(2)) + @pytest.mark.parametrize("divisor", [1, 5, 42]) + def test_lp_token_balance(self, alice, swap, idx, divisor): + initial_amount = swap.balanceOf(alice) + amount = initial_amount // divisor + + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + + assert swap.balanceOf(alice) + amount == initial_amount + + @pytest.mark.parametrize("idx", range(2)) + def test_expected_vs_actual(self, alice, swap, pool_type, pool_tokens, underlying_tokens, idx): + amount = swap.balanceOf(alice) // 10 + + expected = swap.calc_withdraw_one_coin(amount, idx) + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + assert coins[idx].balanceOf(alice) == expected + + @pytest.mark.parametrize("idx", range(2)) + def test_below_min_amount(self, alice, swap, idx): + amount = swap.balanceOf(alice) + + expected = swap.calc_withdraw_one_coin(amount, idx) + with boa.reverts(): + swap.remove_liquidity_one_coin(amount, idx, expected + 1, sender=alice) + + @pytest.mark.parametrize("idx", range(2)) + def test_amount_exceeds_balance(self, bob, swap, idx): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, idx, 0, sender=bob) + + def test_below_zero(self, alice, swap): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) + + def test_above_n_coins(self, alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) + + @pytest.mark.parametrize("idx", range(2)) + def test_event(self, alice, bob, swap, idx, pool_type, pool_tokens, underlying_tokens): + swap.transfer(bob, 10**18, sender=alice) + + tx = swap.remove_liquidity_one_coin(10**18, idx, 0, sender=bob) + + event = tx.events["RemoveLiquidityOne"] + assert event["provider"] == bob + assert event["token_amount"] == 10**18 + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + assert coins[idx].balanceOf(bob) == event["coin_amount"] diff --git a/tests/pools/test_ramp_A.py b/tests/pools/test_ramp_A.py new file mode 100644 index 00000000..03619492 --- /dev/null +++ b/tests/pools/test_ramp_A.py @@ -0,0 +1,93 @@ +import boa + +MIN_RAMP_TIME = 86400 + + +def test_ramp_A(chain, alice, swap): + initial_A = swap.initial_A() // 100 + future_time = chain.time() + MIN_RAMP_TIME + 5 + + tx = swap.ramp_A(initial_A * 2, future_time, sender=alice) + + assert swap.initial_A() // 100 == initial_A + assert swap.future_A() // 100 == initial_A * 2 + assert swap.initial_A_time() == tx.timestamp + assert swap.future_A_time() == future_time + + +def test_ramp_A_final(chain, alice, swap): + initial_A = swap.initial_A() // 100 + future_time = chain.time() + 1000000 + + swap.ramp_A(initial_A * 2, future_time, sender=alice) + + chain.sleep(1000000) + chain.mine() + + assert swap.A() == initial_A * 2 + + +def test_ramp_A_value_up(chain, alice, swap): + initial_A = swap.initial_A() // 100 + future_time = chain.time() + 1000000 + tx = swap.ramp_A(initial_A * 2, future_time, sender=alice) + + initial_time = tx.timestamp + duration = future_time - tx.timestamp + + while chain.time() < future_time: + chain.sleep(100000) + chain.mine() + expected = int(initial_A + ((chain.time() - initial_time) / duration) * initial_A) + assert 0.999 < expected / swap.A() <= 1 + + +def test_ramp_A_value_down(chain, alice, swap): + initial_A = swap.initial_A() // 100 + future_time = chain.time() + 1000000 + tx = swap.ramp_A(initial_A // 10, future_time, sender=alice) + + initial_time = tx.timestamp + duration = future_time - tx.timestamp + + while chain.time() < future_time: + chain.sleep(100000) + chain.mine() + expected = int(initial_A - ((chain.time() - initial_time) / duration) * (initial_A // 10 * 9)) + if expected == 0: + assert swap.A() == initial_A // 10 + else: + assert abs(swap.A() - expected) <= 1 + + +def test_stop_ramp_A(chain, alice, swap): + initial_A = swap.initial_A() // 100 + future_time = chain.time() + 1000000 + swap.ramp_A(initial_A * 2, future_time, sender=alice) + + chain.sleep(31337) + + tx = swap.A.transact(sender=alice) + current_A = tx.return_value + + tx = swap.stop_ramp_A(sender=alice) + + assert swap.initial_A() // 100 == current_A + assert swap.future_A() // 100 == current_A + assert swap.initial_A_time() == tx.timestamp + assert swap.future_A_time() == tx.timestamp + + +def test_ramp_A_only_owner(chain, bob, swap): + with boa.reverts(): + swap.ramp_A(0, chain.time() + 1000000, sender=bob) + + +def test_ramp_A_insufficient_time(chain, alice, swap): + with boa.reverts(): + swap.ramp_A(0, chain.time() + MIN_RAMP_TIME - 1, sender=alice) + + +def test_stop_ramp_A_only_owner(bob, swap): + with boa.reverts(): + swap.stop_ramp_A(sender=bob) diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py new file mode 100644 index 00000000..9c8f2624 --- /dev/null +++ b/tests/pools/test_virtual_price.py @@ -0,0 +1,62 @@ +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +def test_number_go_up(bob, swap, initial_amounts, pool_size): + virtual_price = swap.get_virtual_price() + + for i, amount in enumerate(initial_amounts): + amounts = [0] * pool_size + amounts[i] = amount + swap.add_liquidity(amounts, 0, sender=bob) + + new_virtual_price = swap.get_virtual_price() + assert new_virtual_price > virtual_price + virtual_price = new_virtual_price + + +@pytest.mark.parametrize("idx", range(2)) +def test_remove_one_coin(alice, swap, idx): + amount = swap.balanceOf(alice) // 10 + + virtual_price = swap.get_virtual_price() + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + + assert swap.get_virtual_price() > virtual_price + + +@pytest.mark.parametrize("idx", range(2)) +def test_remove_imbalance(alice, swap, idx, initial_amounts, pool_size): + amounts = [i // 2 for i in initial_amounts] + amounts[idx] = 0 + + virtual_price = swap.get_virtual_price() + swap.remove_liquidity_imbalance(amounts, pool_size * 1_000_000 * 10**18, sender=alice) + + assert swap.get_virtual_price() > virtual_price + + +def test_remove(alice, swap, pool_size, initial_amounts): + withdraw_amount = sum(initial_amounts) // 2 + + virtual_price = swap.get_virtual_price() + swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) + + assert swap.get_virtual_price() >= virtual_price + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange(bob, swap, sending, receiving, decimals): + virtual_price = swap.get_virtual_price() + + amount = 10 ** decimals[sending] + swap.exchange( + sending, + receiving, + amount, + 0, + sender=bob, + ) + + assert swap.get_virtual_price() > virtual_price From 8e57525c5c1a739b3d470f3fb6f42b0499c255d4 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:40:40 +0200 Subject: [PATCH 136/337] fix tests --- tests/pools/test_exchange.py | 27 +++++++++--- tests/pools/test_fees.py | 2 +- tests/pools/test_liquidity.py | 56 +++++++++++-------------- tests/pools/test_ramp_A.py | 79 +++++++++++++++++------------------ 4 files changed, 85 insertions(+), 79 deletions(-) diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index 485077f8..07f11db0 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -8,6 +8,9 @@ class TestExchange: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals): amount = 10 ** decimals[sending] + initial_receiving = ( + pool_tokens[receiving].balanceOf(bob) if pool_type == 0 else underlying_tokens[receiving].balanceOf(bob) + ) min_dy = swap.get_dy(sending, receiving, amount) swap.exchange(sending, receiving, amount, min_dy - 1, sender=bob) @@ -16,13 +19,12 @@ def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, send received = pool_tokens[receiving].balanceOf(bob) else: received = underlying_tokens[receiving].balanceOf(bob) - assert abs(received - min_dy) <= 1 + assert abs(received - min_dy - initial_receiving) <= 1 @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_min_dy_imbalanced( self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals ): - amounts = [10**i for i in decimals] scaler = amounts.copy() # used to scale token amounts when decimals are different @@ -88,17 +90,29 @@ def test_add_liquidity(self, bob, charlie, swap, initial_amounts): assert swap.balanceOf(charlie) > 0 def test_exchange(self, bob, charlie, swap, pool_type, pool_tokens, underlying_tokens, decimals): + initial_balance = pool_tokens[0].balanceOf(bob) if pool_type == 0 else underlying_tokens[0].balanceOf(bob) + swap.exchange(1, 0, 10**18, 0, charlie, sender=bob) if pool_type == 0: assert pool_tokens[0].balanceOf(charlie) > 0 - assert pool_tokens[0].balanceOf(bob) == 0 + assert pool_tokens[0].balanceOf(bob) == initial_balance else: assert underlying_tokens[0].balanceOf(charlie) > 0 - assert underlying_tokens[0].balanceOf(bob) == 0 + assert underlying_tokens[0].balanceOf(bob) == initial_balance def test_remove_liquidity( - self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, pool_size + self, + bob, + swap, + charlie, + pool_type, + pool_tokens, + underlying_tokens, + initial_amounts, + pool_size, + deposit_amounts, ): + swap.add_liquidity(deposit_amounts, 0, sender=bob) initial_amount = swap.balanceOf(bob) withdraw_amount = initial_amount // 4 swap.remove_liquidity(withdraw_amount, [0] * pool_size, charlie, sender=bob) @@ -114,8 +128,9 @@ def test_remove_liquidity( assert swap.totalSupply() == initial_amount - withdraw_amount def test_remove_imbalanced( - self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts + self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, deposit_amounts ): + swap.add_liquidity(deposit_amounts, 0, sender=bob) initial_balance = swap.balanceOf(bob) amounts = [i // 4 for i in initial_amounts] swap.remove_liquidity_imbalance(amounts, initial_balance, charlie, sender=bob) diff --git a/tests/pools/test_fees.py b/tests/pools/test_fees.py index 05d174f5..c2c9aca4 100644 --- a/tests/pools/test_fees.py +++ b/tests/pools/test_fees.py @@ -86,7 +86,7 @@ def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying assert coin.balanceOf(fee_receiver) == 0 fees.append(swap.admin_balances(i)) - swap.withdraw_admin_fees({"from": bob}) + swap.withdraw_admin_fees(sender=bob) if pool_type == 0: for i, coin in enumerate(pool_tokens): assert coin.balanceOf(fee_receiver) == fees[i] diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index 865d9e40..ee79c789 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -7,8 +7,8 @@ from tests.utils.transactions import call_returning_result_and_logs -@pytest.mark.usefixtures("initial_setup") class TestLiquidityMethods: + @pytest.mark.usefixtures("initial_setup") class TestAddLiquidity: def test_add_liquidity( self, @@ -237,6 +237,7 @@ def test_initial( # with boa.reverts(): # swap.add_liquidity(amounts, 0, sender=alice) + @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidity: @pytest.mark.parametrize("min_amount", (0, 1)) def test_remove_liquidity( @@ -253,17 +254,14 @@ def test_remove_liquidity( assert swap.balanceOf(alice) == 0 assert swap.totalSupply() == 0 - def test_remove_partial( - self, alice, swap, pool_type, pool_tokens, underlying_tokens, deposit_amounts, pool_size - ): + def test_remove_partial(self, alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size): initial_amount = swap.balanceOf(alice) withdraw_amount = initial_amount // 2 - swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) - for coin, amount in zip(coins, deposit_amounts): - assert coin.balanceOf(swap) + coin.balanceOf(alice) == amount + for coin in coins: + assert coin.balanceOf(swap) + coin.balanceOf(alice) == initial_amount assert swap.balanceOf(alice) == initial_amount - withdraw_amount assert swap.totalSupply() == initial_amount - withdraw_amount @@ -290,6 +288,7 @@ def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens # event = events[0] # TODO: add event + @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidityImbalance: @pytest.mark.parametrize("divisor", [2, 5, 10]) def test_remove_balanced( @@ -361,19 +360,16 @@ def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens # event = events[0] # TODO: add test event + @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidityOneCoin: @pytest.mark.parametrize("idx", range(2)) def test_amount_received(self, alice, swap, pool_type, pool_tokens, underlying_tokens, decimals, idx): coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - - decimals = decimals[idx] - wrapped = coins[idx] + initial_amount = coins[idx].balanceOf(alice) swap.remove_liquidity_one_coin(10**18, idx, 0, sender=alice) - - ideal = 10**decimals - - assert ideal * 0.99 <= wrapped.balanceOf(alice) <= ideal + ideal = 10 ** decimals[idx] + assert ideal * 0.99 <= coins[idx].balanceOf(alice) - initial_amount <= ideal @pytest.mark.parametrize("idx", range(2)) @pytest.mark.parametrize("divisor", [1, 5, 42]) @@ -387,13 +383,13 @@ def test_lp_token_balance(self, alice, swap, idx, divisor): @pytest.mark.parametrize("idx", range(2)) def test_expected_vs_actual(self, alice, swap, pool_type, pool_tokens, underlying_tokens, idx): + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + initial_amount = coins[idx].balanceOf(alice) amount = swap.balanceOf(alice) // 10 expected = swap.calc_withdraw_one_coin(amount, idx) swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) - - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - assert coins[idx].balanceOf(alice) == expected + assert coins[idx].balanceOf(alice) == expected + initial_amount @pytest.mark.parametrize("idx", range(2)) def test_below_min_amount(self, alice, swap, idx): @@ -408,23 +404,21 @@ def test_amount_exceeds_balance(self, bob, swap, idx): with boa.reverts(): swap.remove_liquidity_one_coin(1, idx, 0, sender=bob) - def test_below_zero(self, alice, swap): - with boa.reverts(): - swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) + # TODO: this hangs + # def test_below_zero(self, alice, swap): + # with boa.reverts(): + # swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) - def test_above_n_coins(self, alice, swap, pool_size): - with boa.reverts(): - swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) + # TODO: this hangs + # def test_above_n_coins(self, alice, swap, pool_size): + # with boa.reverts(): + # swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) @pytest.mark.parametrize("idx", range(2)) def test_event(self, alice, bob, swap, idx, pool_type, pool_tokens, underlying_tokens): swap.transfer(bob, 10**18, sender=alice) - tx = swap.remove_liquidity_one_coin(10**18, idx, 0, sender=bob) + _, events = call_returning_result_and_logs(swap, "remove_liquidity_one_coin", 10**18, idx, 0, sender=bob) - event = tx.events["RemoveLiquidityOne"] - assert event["provider"] == bob - assert event["token_amount"] == 10**18 - - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - assert coins[idx].balanceOf(bob) == event["coin_amount"] + # event = events[0] + # TODO: add test event diff --git a/tests/pools/test_ramp_A.py b/tests/pools/test_ramp_A.py index 03619492..c74144d9 100644 --- a/tests/pools/test_ramp_A.py +++ b/tests/pools/test_ramp_A.py @@ -3,89 +3,86 @@ MIN_RAMP_TIME = 86400 -def test_ramp_A(chain, alice, swap): +def test_ramp_A(owner, swap): initial_A = swap.initial_A() // 100 - future_time = chain.time() + MIN_RAMP_TIME + 5 + future_time = boa.env.vm.state.timestamp + MIN_RAMP_TIME + 5 - tx = swap.ramp_A(initial_A * 2, future_time, sender=alice) + swap.ramp_A(initial_A * 2, future_time, sender=owner) assert swap.initial_A() // 100 == initial_A assert swap.future_A() // 100 == initial_A * 2 - assert swap.initial_A_time() == tx.timestamp + assert swap.initial_A_time() == boa.env.vm.state.timestamp assert swap.future_A_time() == future_time -def test_ramp_A_final(chain, alice, swap): +def test_ramp_A_final(owner, swap): initial_A = swap.initial_A() // 100 - future_time = chain.time() + 1000000 + future_time = boa.env.vm.state.timestamp + 1000000 - swap.ramp_A(initial_A * 2, future_time, sender=alice) - - chain.sleep(1000000) - chain.mine() + swap.ramp_A(initial_A * 2, future_time, sender=owner) + boa.env.time_travel(1000000) assert swap.A() == initial_A * 2 -def test_ramp_A_value_up(chain, alice, swap): +def test_ramp_A_value_up(owner, swap): + initial_timestamp = boa.env.vm.state.timestamp initial_A = swap.initial_A() // 100 - future_time = chain.time() + 1000000 - tx = swap.ramp_A(initial_A * 2, future_time, sender=alice) + future_time = initial_timestamp + 1000000 + swap.ramp_A(initial_A * 2, future_time, sender=owner) - initial_time = tx.timestamp - duration = future_time - tx.timestamp + duration = future_time - initial_timestamp - while chain.time() < future_time: - chain.sleep(100000) - chain.mine() - expected = int(initial_A + ((chain.time() - initial_time) / duration) * initial_A) + while boa.env.vm.state.timestamp < future_time: + boa.env.time_travel(100000) + expected = int(initial_A + ((boa.env.vm.state.timestamp - initial_timestamp) / duration) * initial_A) assert 0.999 < expected / swap.A() <= 1 -def test_ramp_A_value_down(chain, alice, swap): +def test_ramp_A_value_down(owner, swap): + initial_timestamp = boa.env.vm.state.timestamp initial_A = swap.initial_A() // 100 - future_time = chain.time() + 1000000 - tx = swap.ramp_A(initial_A // 10, future_time, sender=alice) + future_time = initial_timestamp + 1000000 + swap.ramp_A(initial_A // 10, future_time, sender=owner) - initial_time = tx.timestamp - duration = future_time - tx.timestamp + duration = future_time - initial_timestamp - while chain.time() < future_time: - chain.sleep(100000) - chain.mine() - expected = int(initial_A - ((chain.time() - initial_time) / duration) * (initial_A // 10 * 9)) + while boa.env.vm.state.timestamp < future_time: + boa.env.time_travel(100000) + expected = int( + initial_A - ((boa.env.vm.state.timestamp - initial_timestamp) / duration) * (initial_A // 10 * 9) + ) if expected == 0: assert swap.A() == initial_A // 10 else: assert abs(swap.A() - expected) <= 1 -def test_stop_ramp_A(chain, alice, swap): +def test_stop_ramp_A(owner, swap): initial_A = swap.initial_A() // 100 - future_time = chain.time() + 1000000 - swap.ramp_A(initial_A * 2, future_time, sender=alice) + future_time = boa.env.vm.state.timestamp + 1000000 + swap.ramp_A(initial_A * 2, future_time, sender=owner) - chain.sleep(31337) + boa.env.time_travel(31337) - tx = swap.A.transact(sender=alice) - current_A = tx.return_value + current_A = swap.A() - tx = swap.stop_ramp_A(sender=alice) + swap.stop_ramp_A(sender=owner) assert swap.initial_A() // 100 == current_A assert swap.future_A() // 100 == current_A - assert swap.initial_A_time() == tx.timestamp - assert swap.future_A_time() == tx.timestamp + assert swap.initial_A_time() == boa.env.vm.state.timestamp + assert swap.future_A_time() == boa.env.vm.state.timestamp -def test_ramp_A_only_owner(chain, bob, swap): +def test_ramp_A_only_owner(bob, swap): with boa.reverts(): - swap.ramp_A(0, chain.time() + 1000000, sender=bob) + swap.ramp_A(0, boa.env.vm.state.timestamp + 1000000, sender=bob) -def test_ramp_A_insufficient_time(chain, alice, swap): +def test_ramp_A_insufficient_time(owner, swap): with boa.reverts(): - swap.ramp_A(0, chain.time() + MIN_RAMP_TIME - 1, sender=alice) + swap.ramp_A(0, boa.env.vm.state.timestamp + MIN_RAMP_TIME - 1, sender=owner) def test_stop_ramp_A_only_owner(bob, swap): From 1436a0d90348d608afd8582262bdacc37f095cd4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:12:14 +0200 Subject: [PATCH 137/337] wip: get_p tests --- tests/test_get_p.py | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/test_get_p.py diff --git a/tests/test_get_p.py b/tests/test_get_p.py new file mode 100644 index 00000000..695dee8b --- /dev/null +++ b/tests/test_get_p.py @@ -0,0 +1,103 @@ +from math import log + +import pytest +from boa.test import strategy +from hypothesis import given, settings + +from tests.utils.tokens import mint_for_testing + +SETTINGS = {"max_examples": 100, "deadline": None} +pytestmark = pytest.mark.usefixtures("initial_setup") + + +def _get_prices_numeric(swap, decimals, i): + + numeric_quote = [] + + if i == 0: # selling 0th token + + sell_token_precision = 10 ** decimals[0] + + for j in range(1, swap.N_COINS): + + amount_in = sell_token_precision // 100 + expected_out = swap.get_dy(0, j, amount_in) + + numeric_quote.append(int(expected_out * sell_token_precision / amount_in)) + + else: + + for j in range(1, swap.N_COINS()): + + sell_token_precision = 10 ** decimals[i] + amount_in = sell_token_precision // 100 + expected_out = swap.get_dy(i, 0, amount_in) + + numeric_quote.append(int(expected_out * sell_token_precision / amount_in)) + + return numeric_quote + + +def _get_prices_amm(swap): + + amm_quote = [] + for i in range(swap.N_COINS() - 1): + amm_quote.append(swap.get_p(i)) + + return amm_quote + + +@given( + amount=strategy("uint256", max_value=10**6), +) +@settings(**SETTINGS) +@pytest.mark.parametrize("i", list(range(8))) +@pytest.mark.parametrize("j", list(range(8))) +def test_get_p_similar(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): + + if i == j or max(i, j) == swap.N_COINS() or amount == 0: + return + + # calc amount in: + amount_in = amount * 10 ** (decimals[i]) + + if amount_in > pool_tokens[i].balanceOf(charlie): + mint_for_testing(charlie, amount_in, pool_tokens[i], False) + + # swap first + pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + swap.exchange(i, j, amount_in, 0, sender=charlie) + + p_amm = _get_prices_amm(swap) + p_numeric = _get_prices_numeric(swap, decimals, i) + + for n in range(swap.N_COINS()): + assert abs(log(p_amm[n] / p_numeric[n])) < 1e-5 + + +@given( + dollar_amount=strategy("uint256", min_value=5 * 10**4, max_value=5 * 10**8), +) +@settings(**SETTINGS) +@pytest.mark.parametrize("i", list(range(8))) +@pytest.mark.parametrize("j", list(range(8))) +def test_get_p_dupm(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): + + if i == j or max(i, j) == swap.N_COINS(): + return + + pass + + +@given( + dollar_amount=strategy("uint256", min_value=5 * 10**4, max_value=5 * 10**8), +) +@settings(**SETTINGS) +@pytest.mark.parametrize("i", list(range(8))) +@pytest.mark.parametrize("j", list(range(8))) +def test_get_p_pupm(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): + + if i == j or max(i, j) == swap.N_COINS(): + return + + pass From 555319c7a37f934be7dee0c07bdeb8a0694c3250 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:20:54 +0200 Subject: [PATCH 138/337] add meta tests --- tests/pools/meta/test_exchange_underlying.py | 72 +++++++++++++++++++ .../meta/test_exchange_underlying_reverts.py | 59 +++++++++++++++ .../pools/meta/test_get_virtual_price_meta.py | 16 +++++ tests/pools/meta/test_receiver_meta.py | 14 ++++ 4 files changed, 161 insertions(+) create mode 100644 tests/pools/meta/test_exchange_underlying.py create mode 100644 tests/pools/meta/test_exchange_underlying_reverts.py create mode 100644 tests/pools/meta/test_get_virtual_price_meta.py create mode 100644 tests/pools/meta/test_receiver_meta.py diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py new file mode 100644 index 00000000..e679baf0 --- /dev/null +++ b/tests/pools/meta/test_exchange_underlying.py @@ -0,0 +1,72 @@ +import itertools + +import pytest +from pytest import approx + +from tests.utils.transactions import call_returning_result_and_logs + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.only_for_pool_type(1) # only for metapools +class TestMetaExchangeUnderlying: + @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) + def test_amounts(self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + + if sending > 0: + underlying_tokens[sending]._mint_for_testing(bob, amount) + + initial_amount = underlying_tokens[sending].balanceOf(bob) + initial_amount_receiving = underlying_tokens[receiving].balanceOf(bob) + + swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + assert underlying_tokens[sending].balanceOf(bob) == initial_amount - amount + + received = underlying_tokens[receiving].balanceOf(bob) - initial_amount_receiving + assert 0.999 <= received / amount < 1 + + @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) + def test_fees( + self, + bob, + swap, + underlying_tokens, + sending, + receiving, + meta_decimals, + base_pool, + base_pool_decimals, + ): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10000 * 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + + admin_idx = min(1, receiving) + admin_fee = swap.admin_balances(admin_idx) + + expected = 2 * 10 ** underlying_decimals[admin_idx] + if admin_idx == 1: + expected = expected * 10**18 // base_pool.get_virtual_price() + assert expected / admin_fee == approx(1, rel=1e-3) + + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) + def test_min_dy_underlying( + self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals + ): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + + expected = swap.get_dy_underlying(sending, receiving, amount) + _, events = call_returning_result_and_logs( + swap, "exchange_underlying", sending, receiving, amount, 0, sender=bob + ) + received = events[0] + + assert abs(expected - received) / received < 0.00001 diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py new file mode 100644 index 00000000..f44af962 --- /dev/null +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -0,0 +1,59 @@ +import itertools + +import boa +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.only_for_pool_type(1) # only for metapools +class TestMetaExchangeUnderlyingReverts: + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) + def test_min_dy_too_high(self, bob, swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + + min_dy = swap.get_dy_underlying(sending, receiving, amount * 1.0001) + with boa.reverts(): + swap.exchange_underlying(sending, receiving, amount, min_dy, sender=bob) + + @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) + def test_insufficient_balance( + self, bob, swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address + ): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + underlying_tokens[sending].transfer(zero_address, underlying_tokens[sending].balanceOf(bob), sender=bob) + + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + with boa.reverts(): + swap.exchange_underlying(sending, receiving, amount + 1, 0, sender=bob) + + @pytest.mark.parametrize("idx", range(4)) + def test_same_coin(self, bob, swap, idx): + with boa.reverts(): + swap.exchange_underlying(idx, idx, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [-1, -(2**127)]) + def test_i_below_zero(self, bob, swap, idx): + with boa.reverts(): + swap.exchange_underlying(idx, 0, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [4, 2**127 - 1]) + def test_i_above_n_coins(self, bob, swap, idx): + with boa.reverts(): + swap.exchange_underlying(idx, 0, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [-1, -(2**127)]) + def test_j_below_zero(self, bob, swap, idx): + with boa.reverts(): + swap.exchange_underlying(0, idx, 0, 0, sender=bob) + + @pytest.mark.parametrize("idx", [4, 2**127 - 1]) + def test_j_above_n_coins(self, bob, swap, idx): + with boa.reverts(): + swap.exchange_underlying(0, idx, 0, 0, sender=bob) diff --git a/tests/pools/meta/test_get_virtual_price_meta.py b/tests/pools/meta/test_get_virtual_price_meta.py new file mode 100644 index 00000000..dc3f5f43 --- /dev/null +++ b/tests/pools/meta/test_get_virtual_price_meta.py @@ -0,0 +1,16 @@ +import itertools + +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +def test_exchange_underlying(bob, swap, sending, receiving, meta_decimals, base_pool_decimals): + underlying_decimals = [meta_decimals] + base_pool_decimals + virtual_price = swap.get_virtual_price() + + amount = 10 ** underlying_decimals[sending] + swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + + assert swap.get_virtual_price() > virtual_price diff --git a/tests/pools/meta/test_receiver_meta.py b/tests/pools/meta/test_receiver_meta.py new file mode 100644 index 00000000..d373c7a7 --- /dev/null +++ b/tests/pools/meta/test_receiver_meta.py @@ -0,0 +1,14 @@ +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +def test_exchange_underlying(alice, charlie, swap, underlying_tokens, meta_decimals, base_pool_decimals): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[1] + underlying_tokens[1]._mint_for_testing(alice, amount, sender=alice) + + swap.exchange_underlying(1, 0, amount, 0, charlie, sender=alice) + assert underlying_tokens[0].balanceOf(charlie) > 0 + assert underlying_tokens[0].balanceOf(alice) == 0 From ed769e470115d09a4fec6bf1bd8ff35a1bc99fb0 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 18 Aug 2023 13:02:31 +0200 Subject: [PATCH 139/337] add test for get_p --- tests/test_get_p.py | 101 +++++++++++--------------------------------- 1 file changed, 24 insertions(+), 77 deletions(-) diff --git a/tests/test_get_p.py b/tests/test_get_p.py index 695dee8b..fdf2794e 100644 --- a/tests/test_get_p.py +++ b/tests/test_get_p.py @@ -1,3 +1,4 @@ +import random from math import log import pytest @@ -10,94 +11,40 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -def _get_prices_numeric(swap, decimals, i): - - numeric_quote = [] - - if i == 0: # selling 0th token - - sell_token_precision = 10 ** decimals[0] - - for j in range(1, swap.N_COINS): - - amount_in = sell_token_precision // 100 - expected_out = swap.get_dy(0, j, amount_in) - - numeric_quote.append(int(expected_out * sell_token_precision / amount_in)) - - else: - - for j in range(1, swap.N_COINS()): - - sell_token_precision = 10 ** decimals[i] - amount_in = sell_token_precision // 100 - expected_out = swap.get_dy(i, 0, amount_in) - - numeric_quote.append(int(expected_out * sell_token_precision / amount_in)) - - return numeric_quote - - -def _get_prices_amm(swap): - - amm_quote = [] - for i in range(swap.N_COINS() - 1): - amm_quote.append(swap.get_p(i)) - - return amm_quote - - @given( - amount=strategy("uint256", max_value=10**6), + amount=strategy("uint256", min_value=1, max_value=10**6), ) @settings(**SETTINGS) -@pytest.mark.parametrize("i", list(range(8))) -@pytest.mark.parametrize("j", list(range(8))) -def test_get_p_similar(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): +def test_get_p_similar(swap, views_implementation, bob, pool_tokens, decimals, amount): - if i == j or max(i, j) == swap.N_COINS() or amount == 0: - return + for token in pool_tokens: + if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: + return # TODO: rebasing tokens that rebase downwards are causing trouble here. + + i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: amount_in = amount * 10 ** (decimals[i]) - if amount_in > pool_tokens[i].balanceOf(charlie): - mint_for_testing(charlie, amount_in, pool_tokens[i], False) + if amount_in > pool_tokens[i].balanceOf(bob): + mint_for_testing(bob, amount_in, pool_tokens[i], False) # swap first - pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) - swap.exchange(i, j, amount_in, 0, sender=charlie) - - p_amm = _get_prices_amm(swap) - p_numeric = _get_prices_numeric(swap, decimals, i) + pool_tokens[i].approve(swap, 2**256 - 1, sender=bob) + swap.exchange(i, j, amount_in, 0, sender=bob) - for n in range(swap.N_COINS()): - assert abs(log(p_amm[n] / p_numeric[n])) < 1e-5 + # numeric prices: + p_numeric = [] + for n in range(1, swap.N_COINS()): + expected_jth_out = views_implementation.get_dy(0, n, 10**18, swap) + p_numeric.append(swap.stored_rates(0) / expected_jth_out) -@given( - dollar_amount=strategy("uint256", min_value=5 * 10**4, max_value=5 * 10**8), -) -@settings(**SETTINGS) -@pytest.mark.parametrize("i", list(range(8))) -@pytest.mark.parametrize("j", list(range(8))) -def test_get_p_dupm(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): - - if i == j or max(i, j) == swap.N_COINS(): - return - - pass - - -@given( - dollar_amount=strategy("uint256", min_value=5 * 10**4, max_value=5 * 10**8), -) -@settings(**SETTINGS) -@pytest.mark.parametrize("i", list(range(8))) -@pytest.mark.parametrize("j", list(range(8))) -def test_get_p_pupm(swap, charlie, pool_type, pool_tokens, underlying_tokens, decimals, i, j, amount): - - if i == j or max(i, j) == swap.N_COINS(): - return + # amm prices: + p_amm = [] + for n in range(swap.N_COINS() - 1): + p_amm.append(swap.get_p(n) * swap.stored_rates(n + 1) / 10**36) - pass + # compare + for n in range(swap.N_COINS() - 1): + assert abs(log(p_amm[n] / p_numeric[n])) < 1e-3, f"p_amm: {p_amm}, p_numeric: {p_numeric}" From 912b9e07775ceaf6619a6ecc9ce46dae098b928f Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 18 Aug 2023 23:51:24 +0200 Subject: [PATCH 140/337] fix factory --- .github/workflows/test_factory.yaml | 5 +++++ tests/test_factory.py | 19 ++---------------- tests/test_factory_forked.py | 30 +++++++++++++++++++++++++++++ tests/test_token.py | 2 +- 4 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 tests/test_factory_forked.py diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index d3b7a444..36bccbe5 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -42,3 +42,8 @@ jobs: run: | source .venv/bin/activate pytest tests/test_factory.py --token-types=plain -n auto + + - name: Run Tests Forked + run: | + source .venv/bin/activate + pytest tests/test_factory_forked.py --token-types=plain -n auto diff --git a/tests/test_factory.py b/tests/test_factory.py index 9ff9195f..4a80fc05 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -95,10 +95,10 @@ def test_get_underlying_coins(self, factory, swap, underlying_tokens): def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals - def test_get_metapool_rates(self, factory, swap, base_pool, add_initial_liquidity_alice): + def test_get_metapool_rates(self, factory, swap, base_pool, initial_setup): assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] - def test_get_underlying_balances(self, factory, swap, base_pool, add_initial_liquidity_alice): + def test_get_underlying_balances(self, factory, swap, base_pool, initial_setup): assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) @@ -159,21 +159,6 @@ def empty_factory_with_implementations( return empty_factory - def test_add_base_pool(self, empty_factory, owner, forked_chain): - susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" - lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" - coins = [ - "0x6B175474E89094C44Da98b954EedeAC495271d0F", - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "0xdAC17F958D2ee523a2206206994597C13D831ec7", - "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", - ] - - assert empty_factory.base_pool_count() == 0 - empty_factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) - assert empty_factory.base_pool_count() == 1 - assert empty_factory.base_pool_list(0) == susd_pool - def test_add_base_pool_already_exists( self, owner, diff --git a/tests/test_factory_forked.py b/tests/test_factory_forked.py new file mode 100644 index 00000000..ea947730 --- /dev/null +++ b/tests/test_factory_forked.py @@ -0,0 +1,30 @@ +import boa +import pytest + + +class TestFactoryForked: + @pytest.fixture + def empty_factory(self, deployer, fee_receiver, owner): + with boa.env.prank(deployer): + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + owner, + ) + return _factory + + @pytest.mark.only_for_pool_type(1) + def test_add_base_pool(self, empty_factory, owner, forked_chain): + susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" + lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" + coins = [ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", + ] + + assert empty_factory.base_pool_count() == 0 + empty_factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) + assert empty_factory.base_pool_count() == 1 + assert empty_factory.base_pool_list(0) == susd_pool diff --git a/tests/test_token.py b/tests/test_token.py index 3c9ca6d3..fa67081b 100644 --- a/tests/test_token.py +++ b/tests/test_token.py @@ -5,7 +5,7 @@ from tests.utils.transactions import call_returning_result_and_logs -added_liquidity = pytest.mark.usefixtures("add_initial_liquidity_alice") +added_liquidity = pytest.mark.usefixtures("initial_setup") class TestPoolToken: From 50f07243588ba22045594bbf88d9358cbebb22d3 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:07:41 +0200 Subject: [PATCH 141/337] fix exchange --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 4 +- contracts/mocks/ERC20Rebasing.vy | 4 +- tests/fixtures/constants.py | 25 ++++++++- tests/pools/test_exchange.py | 67 +++++++++++++++++-------- 5 files changed, 74 insertions(+), 28 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9abd355d..cb4f08bb 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -820,7 +820,7 @@ def remove_liquidity_imbalance( # base_fee * difference / FEE_DENOMINATOR xs = new_balance + old_balances[i] dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees[i] = unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR) + fees.append(unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR)) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 1ba254f2..96f0993d 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -707,7 +707,7 @@ def remove_liquidity_imbalance( xs = new_balance + old_balances[i] dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees[i] = base_fee * difference / FEE_DENOMINATOR + fees.append(base_fee * difference / FEE_DENOMINATOR) self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] @@ -755,7 +755,7 @@ def remove_liquidity( value: uint256 = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts[i] = value + amounts.append(value) self._transfer_out(i, value, _receiver) total_supply -= _burn_amount diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index 73b3507d..232b9178 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -124,9 +124,9 @@ def share_price() -> uint256: @internal def _rebase(): if IS_UP: - self.totalCoin = self.totalCoin * 101 / 100 + self.totalCoin = self.totalCoin * 1000001 / 1000000 else: - self.totalCoin = self.totalCoin * 99 / 100 + self.totalCoin = self.totalCoin * 999999 / 1000000 @external diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 9f2360a9..f7d3d228 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -18,8 +18,29 @@ def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: @pytest.fixture(scope="module") -def deposit_amounts(initial_amounts: list[int]) -> list[int]: - return [ia // 2 for ia in initial_amounts] +def deposit_amounts( + initial_amounts: list[int], pool_type, pool_token_types, metapool_token_type, pool_tokens, underlying_tokens +) -> list[int]: + amounts = [] + + # This (almost) adds liquidity in balance for oracle tokens + if pool_type == 0: + i = 0 + for ptt, pt in zip(pool_token_types, pool_tokens): + if ptt != 1: + amounts.append(initial_amounts[i] // 2) + else: + amounts.append(initial_amounts[i] * 10**18 // pt.exchangeRate() // 2) + i += 1 + else: + if metapool_token_type != 1: + amounts.append(initial_amounts[0] // 2) + else: + amounts.append(initial_amounts[0] * 10**18 // underlying_tokens[0].exchangeRate() // 2) + + amounts.append(initial_amounts[1] // 2) + + return amounts @pytest.fixture(scope="module") diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index 07f11db0..e4149760 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -7,7 +7,7 @@ class TestExchange: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals): - amount = 10 ** decimals[sending] + amount = 1000 * 10 ** decimals[sending] initial_receiving = ( pool_tokens[receiving].balanceOf(bob) if pool_type == 0 else underlying_tokens[receiving].balanceOf(bob) ) @@ -25,11 +25,11 @@ def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, send def test_min_dy_imbalanced( self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals ): - amounts = [10**i for i in decimals] + amounts = [1_000_000 * 10**i for i in decimals] scaler = amounts.copy() # used to scale token amounts when decimals are different amounts[sending] = 0 - amounts[receiving] = amounts[receiving] * 1_000_000 + amounts[receiving] = amounts[receiving] swap.add_liquidity(amounts, 0, sender=bob) @@ -80,25 +80,42 @@ def test_j_above_n_coins(self, bob, swap, idx): def test_nonpayable(self, swap, bob): with boa.reverts(): - swap.exchange(0, 1, 0, 0, sender=bob) + swap.exchange(0, 1, 0, 0, sender=bob, value=1) class TestReceiver: - def test_add_liquidity(self, bob, charlie, swap, initial_amounts): - swap.add_liquidity(initial_amounts, 0, charlie, sender=bob) + def test_add_liquidity(self, bob, charlie, swap, deposit_amounts): + swap.add_liquidity(deposit_amounts, 0, charlie, sender=bob) assert swap.balanceOf(bob) == 0 assert swap.balanceOf(charlie) > 0 - def test_exchange(self, bob, charlie, swap, pool_type, pool_tokens, underlying_tokens, decimals): + def test_exchange( + self, + bob, + charlie, + swap, + pool_type, + pool_tokens, + underlying_tokens, + decimals, + pool_token_types, + metapool_token_type, + ): initial_balance = pool_tokens[0].balanceOf(bob) if pool_type == 0 else underlying_tokens[0].balanceOf(bob) - swap.exchange(1, 0, 10**18, 0, charlie, sender=bob) + swap.exchange(1, 0, 1000 * 10**18, 0, charlie, sender=bob) if pool_type == 0: assert pool_tokens[0].balanceOf(charlie) > 0 - assert pool_tokens[0].balanceOf(bob) == initial_balance + if pool_token_types[0] != 2: + assert pool_tokens[0].balanceOf(bob) == initial_balance + else: + assert pool_tokens[0].balanceOf(bob) == pytest.approx(initial_balance, rel=2e-2) else: assert underlying_tokens[0].balanceOf(charlie) > 0 - assert underlying_tokens[0].balanceOf(bob) == initial_balance + if metapool_token_type != 2: + assert underlying_tokens[0].balanceOf(bob) == initial_balance + else: + assert underlying_tokens[0].balanceOf(bob) == pytest.approx(initial_balance, rel=2e-2) def test_remove_liquidity( self, @@ -117,15 +134,23 @@ def test_remove_liquidity( withdraw_amount = initial_amount // 4 swap.remove_liquidity(withdraw_amount, [0] * pool_size, charlie, sender=bob) + i = 0 if pool_type == 0: - for coin, amount in zip(pool_tokens, initial_amounts): - assert coin.balanceOf(swap) + coin.balanceOf(charlie) == amount + for coin, amount in zip(pool_tokens, deposit_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx( + deposit_amounts[0] * 2, rel=1.5e-2 + ) + i += 1 else: - for coin, amount in zip(underlying_tokens[:2], initial_amounts): - assert coin.balanceOf(swap) + coin.balanceOf(charlie) == amount + for coin, amount in zip(underlying_tokens[:2], deposit_amounts): + print(coin.balanceOf(swap), coin.balanceOf(charlie), i) + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx( + deposit_amounts[0] * 2, rel=1.5e-2 + ) + i += 1 - assert swap.balanceOf(bob) == initial_amount - withdraw_amount - assert swap.totalSupply() == initial_amount - withdraw_amount + assert swap.balanceOf(bob) == pytest.approx(deposit_amounts[0] * 2 - withdraw_amount, rel=1.5e-2) + assert swap.totalSupply() == pytest.approx(deposit_amounts[0] * 4 - withdraw_amount, rel=1.5e-2) def test_remove_imbalanced( self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, deposit_amounts @@ -137,14 +162,14 @@ def test_remove_imbalanced( if pool_type == 0: for i, coin in enumerate(pool_tokens): - assert coin.balanceOf(charlie) == amounts[i] - assert coin.balanceOf(swap) == initial_amounts[i] - amounts[i] + assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) else: for i, coin in enumerate(underlying_tokens[:2]): - assert coin.balanceOf(charlie) == amounts[i] - assert coin.balanceOf(swap) == initial_amounts[i] - amounts[i] + assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) - assert swap.balanceOf(bob) / initial_balance == 0.75 + assert swap.balanceOf(bob) / initial_balance == pytest.approx(0.5, rel=1.5e-2) def test_remove_one_coin(self, alice, charlie, swap, pool_type, pool_tokens, underlying_tokens): swap.remove_liquidity_one_coin(10**18, 0, 0, charlie, sender=alice) From 038132bb7955180f4a29acf1bfac245bd5b2c821 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:12:23 +0200 Subject: [PATCH 142/337] fix add liquidity --- tests/pools/test_liquidity.py | 79 ++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index ee79c789..0c6de890 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -202,28 +202,32 @@ def test_initial( swap, pool_type, pool_tokens, + underlying_tokens, pool_token_types, metapool_token_type, min_amount, decimals, meta_decimals, + deposit_amounts, initial_amounts, ): - swap_decimals = decimals if pool_type == 0 else [meta_decimals, 18] - amounts = [10**i for i in swap_decimals] - swap.add_liquidity( - amounts, + deposit_amounts, len(pool_tokens) * min_amount, sender=alice, ) token_types = pool_token_types if pool_type == 0 else [metapool_token_type, 18] - for coin, amount, initial, pool_token_type in zip(pool_tokens, amounts, initial_amounts, token_types): - if pool_token_type == 0: - assert coin.balanceOf(alice) == initial - amount - assert coin.balanceOf(swap) == amount + for coin, und_coin, amount, initial, pool_token_type in zip( + pool_tokens, underlying_tokens, deposit_amounts, initial_amounts, token_types + ): + if pool_type == 0: + assert coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) + else: + assert und_coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) + assert und_coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) # TODO: boa hangs with it, with added single print it passes # @pytest.mark.parametrize("idx", (0, 1)) @@ -248,7 +252,7 @@ def test_remove_liquidity( coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] for coin, amount in zip(coins, deposit_amounts): - assert coin.balanceOf(alice) == amount + assert coin.balanceOf(alice) == pytest.approx(amount * 2, rel=1.5e-2) assert coin.balanceOf(swap) == 0 assert swap.balanceOf(alice) == 0 @@ -261,7 +265,7 @@ def test_remove_partial(self, alice, swap, pool_type, pool_tokens, underlying_to swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) for coin in coins: - assert coin.balanceOf(swap) + coin.balanceOf(alice) == initial_amount + assert coin.balanceOf(swap) + coin.balanceOf(alice) == pytest.approx(initial_amount, rel=1.5e-2) assert swap.balanceOf(alice) == initial_amount - withdraw_amount assert swap.totalSupply() == initial_amount - withdraw_amount @@ -274,25 +278,23 @@ def test_below_min_amount(self, alice, swap, initial_amounts, idx): with boa.reverts(): swap.remove_liquidity(swap.balanceOf(alice), min_amount, sender=alice) - def test_amount_exceeds_balance(self, alice, swap, plain_pool_size): + def test_amount_exceeds_balance(self, alice, swap, pool_size): with boa.reverts(): - swap.remove_liquidity(swap.balanceOf(alice) + 1, [0] * plain_pool_size, sender=alice) + swap.remove_liquidity(swap.balanceOf(alice) + 1, [0] * pool_size, sender=alice) - def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens, plain_pool_size): + def test_event(self, alice, bob, swap, pool_type, pool_size): swap.transfer(bob, 10**18, sender=alice) _, events = call_returning_result_and_logs( - swap, "remove_liquidity", 10**18, [0] * plain_pool_size, sender=alice + swap, "remove_liquidity", 10**18, [0] * pool_size, sender=alice ) - # coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - # event = events[0] - # TODO: add event + assert f"RemoveLiquidity(provider={alice}" in repr(events[3]) @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidityImbalance: @pytest.mark.parametrize("divisor", [2, 5, 10]) def test_remove_balanced( - self, alice, swap, pool_type, pool_tokens, underlying_tokens, divisor, deposit_amounts + self, alice, swap, pool_type, pool_tokens, underlying_tokens, divisor, deposit_amounts, initial_amounts ): initial_balance = swap.balanceOf(alice) amounts = [i // divisor for i in deposit_amounts] @@ -301,14 +303,25 @@ def test_remove_balanced( coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] for i, coin in enumerate(coins): - assert coin.balanceOf(alice) == amounts[i] - assert coin.balanceOf(swap) == deposit_amounts[i] - amounts[i] + assert coin.balanceOf(alice) == pytest.approx( + amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2 + ) + assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) - assert swap.balanceOf(alice) / initial_balance == 1 - 1 / divisor + assert swap.balanceOf(alice) / initial_balance == pytest.approx(1 - 1 / divisor, rel=1.5e-2) @pytest.mark.parametrize("idx", range(2)) def test_remove_one( - self, alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size, idx, deposit_amounts + self, + alice, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_size, + idx, + deposit_amounts, + initial_amounts, ): amounts = [0] * pool_size amounts[idx] = deposit_amounts[idx] // 2 @@ -319,8 +332,10 @@ def test_remove_one( coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] for i, coin in enumerate(coins): - assert coin.balanceOf(alice) == amounts[i] - assert coin.balanceOf(swap) == deposit_amounts[i] - amounts[i] + assert coin.balanceOf(alice) == pytest.approx( + amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2 + ) + assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) actual_balance = swap.balanceOf(alice) actual_total_supply = swap.totalSupply() @@ -346,19 +361,16 @@ def test_no_totalsupply(self, alice, swap, pool_size): with boa.reverts(): swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) - def test_event(self, alice, bob, swap, pool_type, pool_tokens, underlying_tokens, pool_size, deposit_amounts): + def test_event(self, alice, bob, swap, pool_type, pool_size, deposit_amounts): swap.transfer(bob, swap.balanceOf(alice), sender=alice) amounts = [i // 5 for i in deposit_amounts] max_burn = pool_size * 1_000_000 * 10**18 - # coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - _, events = call_returning_result_and_logs( swap, "remove_liquidity_imbalance", amounts, max_burn, sender=bob ) - # event = events[0] - # TODO: add test event + assert f"RemoveLiquidityImbalance(provider={bob}" in repr(events[3]) @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidityOneCoin: @@ -415,10 +427,11 @@ def test_amount_exceeds_balance(self, bob, swap, idx): # swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) @pytest.mark.parametrize("idx", range(2)) - def test_event(self, alice, bob, swap, idx, pool_type, pool_tokens, underlying_tokens): + def test_event(self, alice, bob, swap, idx, pool_type): swap.transfer(bob, 10**18, sender=alice) - _, events = call_returning_result_and_logs(swap, "remove_liquidity_one_coin", 10**18, idx, 0, sender=bob) - # event = events[0] - # TODO: add test event + if pool_type == 0: + assert f"RemoveLiquidityOne(provider={bob}" in repr(events[2]) + else: + assert f"RemoveLiquidityOne(provider={bob}" in repr(events[3]) From b551aafd8f48d7fb82e9d97b0735d2e49ed16795 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:30:10 +0200 Subject: [PATCH 143/337] fix virtual price --- tests/pools/test_virtual_price.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py index 9c8f2624..ce5ba050 100644 --- a/tests/pools/test_virtual_price.py +++ b/tests/pools/test_virtual_price.py @@ -27,8 +27,8 @@ def test_remove_one_coin(alice, swap, idx): @pytest.mark.parametrize("idx", range(2)) -def test_remove_imbalance(alice, swap, idx, initial_amounts, pool_size): - amounts = [i // 2 for i in initial_amounts] +def test_remove_imbalance(alice, swap, idx, deposit_amounts, pool_size): + amounts = [i // 2 for i in deposit_amounts] amounts[idx] = 0 virtual_price = swap.get_virtual_price() @@ -37,8 +37,8 @@ def test_remove_imbalance(alice, swap, idx, initial_amounts, pool_size): assert swap.get_virtual_price() > virtual_price -def test_remove(alice, swap, pool_size, initial_amounts): - withdraw_amount = sum(initial_amounts) // 2 +def test_remove(alice, swap, pool_size, deposit_amounts): + withdraw_amount = sum(deposit_amounts) // 2 virtual_price = swap.get_virtual_price() swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) From fc4447fb6634b89099ee00996e9905696afdfbca Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:02:14 +0200 Subject: [PATCH 144/337] fix meta --- tests/pools/meta/test_exchange_underlying.py | 7 +------ .../pools/meta/test_exchange_underlying_reverts.py | 2 +- tests/pools/meta/test_get_virtual_price_meta.py | 5 ++++- tests/pools/meta/test_receiver_meta.py | 14 +++++++------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index e679baf0..595eb18d 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -3,8 +3,6 @@ import pytest from pytest import approx -from tests.utils.transactions import call_returning_result_and_logs - pytestmark = pytest.mark.usefixtures("initial_setup") @@ -64,9 +62,6 @@ def test_min_dy_underlying( underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) expected = swap.get_dy_underlying(sending, receiving, amount) - _, events = call_returning_result_and_logs( - swap, "exchange_underlying", sending, receiving, amount, 0, sender=bob - ) - received = events[0] + received = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert abs(expected - received) / received < 0.00001 diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py index f44af962..41175ba2 100644 --- a/tests/pools/meta/test_exchange_underlying_reverts.py +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -15,7 +15,7 @@ def test_min_dy_too_high(self, bob, swap, underlying_tokens, meta_decimals, base amount = 10 ** underlying_decimals[sending] underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - min_dy = swap.get_dy_underlying(sending, receiving, amount * 1.0001) + min_dy = swap.get_dy_underlying(sending, receiving, int(amount * 1.0001)) with boa.reverts(): swap.exchange_underlying(sending, receiving, amount, min_dy, sender=bob) diff --git a/tests/pools/meta/test_get_virtual_price_meta.py b/tests/pools/meta/test_get_virtual_price_meta.py index dc3f5f43..4349c241 100644 --- a/tests/pools/meta/test_get_virtual_price_meta.py +++ b/tests/pools/meta/test_get_virtual_price_meta.py @@ -6,11 +6,14 @@ @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_exchange_underlying(bob, swap, sending, receiving, meta_decimals, base_pool_decimals): +def test_exchange_underlying(bob, swap, sending, receiving, meta_decimals, base_pool_decimals, underlying_tokens): underlying_decimals = [meta_decimals] + base_pool_decimals virtual_price = swap.get_virtual_price() amount = 10 ** underlying_decimals[sending] + if sending > 0: + underlying_tokens[sending + 1]._mint_for_testing(bob, amount) + swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert swap.get_virtual_price() > virtual_price diff --git a/tests/pools/meta/test_receiver_meta.py b/tests/pools/meta/test_receiver_meta.py index d373c7a7..43feca65 100644 --- a/tests/pools/meta/test_receiver_meta.py +++ b/tests/pools/meta/test_receiver_meta.py @@ -3,12 +3,12 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -def test_exchange_underlying(alice, charlie, swap, underlying_tokens, meta_decimals, base_pool_decimals): - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10 ** underlying_decimals[1] - underlying_tokens[1]._mint_for_testing(alice, amount, sender=alice) +def test_exchange_underlying(bob, charlie, swap, underlying_tokens, meta_decimals, base_pool_decimals): + initial_amount = underlying_tokens[0].balanceOf(bob) - swap.exchange_underlying(1, 0, amount, 0, charlie, sender=alice) + amount = 10 ** base_pool_decimals[0] + underlying_tokens[2]._mint_for_testing(bob, amount) + + swap.exchange_underlying(1, 0, amount, 0, charlie, sender=bob) assert underlying_tokens[0].balanceOf(charlie) > 0 - assert underlying_tokens[0].balanceOf(alice) == 0 + assert underlying_tokens[0].balanceOf(bob) == initial_amount From b3635d9b5165d388f8f40204d5be3bcdfa374561 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:59:05 +0200 Subject: [PATCH 145/337] fix last p --- contracts/main/CurveStableSwapMetaNG.vy | 8 ++++++++ contracts/main/CurveStableSwapNG.vy | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index cb4f08bb..d76accbe 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1234,6 +1234,14 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint2 if new_y > 0: last_p = self._get_p(xp, amp, D1) + else: + for j in range(1, MAX_COINS_128): + if j == N_COINS_128: + break + + pp: uint256 = self.last_prices_packed[j - 1] + last_p.append(pp & (2**128 - 1)) + return dy, dy_0 - dy, last_p diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 96f0993d..ebbfc0d0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1188,6 +1188,14 @@ def _calc_withdraw_one_coin( if new_y > 0: last_p = self._get_p(xp, amp, D1) + else: + for j in range(1, MAX_COINS_128): + if j == N_COINS_128: + break + + pp: uint256 = self.last_prices_packed[j - 1] + last_p.append(pp & (2**128 - 1)) + return dy, dy_0 - dy, last_p From e4d5101720167c8fcfe1d89484b6f2a8ea008643 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:14:04 +0200 Subject: [PATCH 146/337] add ema test --- contracts/main/CurveStableSwapMetaNG.vy | 4 +- contracts/main/CurveStableSwapNG.vy | 13 +++-- tests/fixtures/pools.py | 21 ++++---- tests/test_get_p.py | 70 +++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 9abd355d..fcaca9ad 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1315,13 +1315,13 @@ def _ma_price() -> uint256: @view @external def last_price(i: uint256) -> uint256: - return self.last_prices_packed[0] & (2**128 - 1) + return self.last_prices_packed[i] & (2**128 - 1) @view @external def ema_price(i: uint256) -> uint256: - return (self.last_prices_packed[0] >> 128) + return (self.last_prices_packed[i] >> 128) @external diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 1ba254f2..e11a22d0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -168,7 +168,7 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price +last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -248,9 +248,9 @@ def __init__( N_COINS_128 = convert(__n_coins, int128) for i in range(MAX_COINS): - if i == __n_coins: + if i == __n_coins - 1: # __n_coins == 2 break - self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) + self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) # length of 1 maximally rate_multipliers = _rate_multipliers asset_types = _asset_types @@ -1202,7 +1202,7 @@ def pack_prices(p1: uint256, p2: uint256) -> uint256: @internal -@view +@pure def _get_p( xp: DynArray[uint256, MAX_COINS], amp: uint256, @@ -1222,7 +1222,9 @@ def _get_p( p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) xp0_A: uint256 = ANN * xp[0] / A_PRECISION + for i in range(1, MAX_COINS): + if i == N_COINS: break @@ -1239,7 +1241,8 @@ def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): ma_last_time: uint256 = self.ma_last_time for i in range(MAX_COINS): - if i == N_COINS - 1: + + if i == N_COINS - 1: # 1 (N_COINS is 2) break if last_prices[i] != 0: diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 9f239bbc..2ec0edc6 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -70,36 +70,35 @@ def swap( fee = 1000000 method_id = bytes(b"") oracle = zero_address - asset_type = 0 # 0 = Plain, 1 = Oracle, 2 = Rebasing - metapool_token_type = underlying_tokens[0].asset_type() - if metapool_token_type == 0: + metapool_token = underlying_tokens[0] + asset_type = metapool_token.asset_type() # 0 = Plain, 1 = Oracle, 2 = Rebasing + + if asset_type == 0: A = 2000 fee = 1000000 - asset_type = 0 - elif metapool_token_type == 1: + + elif asset_type == 1: A = 1000 fee = 3000000 - asset_type = 1 method_id = oracle_method_id - oracle = underlying_tokens[0].address + oracle = metapool_token.address - elif metapool_token_type == 2: + elif asset_type == 2: A = 500 fee = 4000000 - asset_type = 2 pool = factory.deploy_metapool( base_pool.address, # _base_pool: address "test", # _name: String[32], "test", # _symbol: String[10], - underlying_tokens[0].address, # _coin: address, + metapool_token.address, # _coin: address, A, # _A: uint256, fee, # _fee: uint256, offpeg_fee_multiplier, 866, # _ma_exp_time: uint256, 0, # _implementation_idx: uint256 - metapool_token_type, # _asset_type: uint8 + asset_type, # _asset_type: uint8 method_id, # _method_id: bytes4 oracle, # _oracle: address ) diff --git a/tests/test_get_p.py b/tests/test_get_p.py index fdf2794e..fd5a9410 100644 --- a/tests/test_get_p.py +++ b/tests/test_get_p.py @@ -1,21 +1,37 @@ import random -from math import log +from math import exp, log +import boa import pytest from boa.test import strategy from hypothesis import given, settings from tests.utils.tokens import mint_for_testing -SETTINGS = {"max_examples": 100, "deadline": None} +SETTINGS = {"max_examples": 1000, "deadline": None} pytestmark = pytest.mark.usefixtures("initial_setup") +def approx(x1: int, x2: int, precision: int, abs_precision=None): + if precision >= 1: + return True + result = False + if abs_precision is not None: + result = abs(x2 - x1) <= abs_precision + else: + abs_precision = 0 + if x2 == 0: + return abs(x1) <= abs_precision + elif x1 == 0: + return abs(x2) <= abs_precision + return result or (abs(log(x1 / x2)) <= precision) + + @given( amount=strategy("uint256", min_value=1, max_value=10**6), ) @settings(**SETTINGS) -def test_get_p_similar(swap, views_implementation, bob, pool_tokens, decimals, amount): +def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): for token in pool_tokens: if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: @@ -48,3 +64,51 @@ def test_get_p_similar(swap, views_implementation, bob, pool_tokens, decimals, a # compare for n in range(swap.N_COINS() - 1): assert abs(log(p_amm[n] / p_numeric[n])) < 1e-3, f"p_amm: {p_amm}, p_numeric: {p_numeric}" + + +@given( + amount=strategy("uint256", min_value=1, max_value=10**5), + dt0=strategy("uint256", min_value=0, max_value=10**6), + dt=strategy("uint256", min_value=0, max_value=10**6), +) +@settings(**SETTINGS) +def test_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): + + for token in pool_tokens: + if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: + return # TODO: rebasing tokens that rebase downwards are causing trouble here. + + i, j = random.sample(range(swap.N_COINS()), 2) + + # calc amount in: + amount_in = amount * 10 ** (decimals[i]) + + # mint tokens for bob if he needs: + if amount_in > pool_tokens[i].balanceOf(bob): + mint_for_testing(bob, amount_in, pool_tokens[i], False) + + boa.env.time_travel(dt0) + swap.exchange(i, j, amount, 0, sender=bob) + + # amm prices: + p_amm = [] + for n in range(swap.N_COINS() - 1): + + _p = swap.get_p(n) + + assert approx(swap.last_price(n), _p, 1e-5) + assert approx(swap.price_oracle(n), 10**18, 1e-5) + + p_amm.append(_p) + + # time travel dt amount: + boa.env.time_travel(dt) + + # calculate weights based on time travelled: + w = exp(-dt / 866) + + # check: + for n in range(swap.N_COINS() - 1): + + p1 = int(10**18 * w + p_amm[n] * (1 - w)) + assert approx(swap.price_oracle(n), p1, 1e-5) From 586d18958736972d1642089f429fdf768ef8e1c0 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Mon, 21 Aug 2023 23:46:55 +0200 Subject: [PATCH 147/337] add oracle --- tests/conftest.py | 2 +- tests/pools/oracle/test_oracle.py | 134 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/pools/oracle/test_oracle.py diff --git a/tests/conftest.py b/tests/conftest.py index dc7b62f3..189f66e1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -173,7 +173,7 @@ def skip_by_token_type(request, swap): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: asset_types = swap._immutables.asset_types - if not all(asset_type in only_for_token_types.args for asset_type in asset_types): + if not any(asset_type in only_for_token_types.args for asset_type in asset_types): pytest.skip("skipped because no tokens for these types") diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py new file mode 100644 index 00000000..00f8e459 --- /dev/null +++ b/tests/pools/oracle/test_oracle.py @@ -0,0 +1,134 @@ +import boa +import pytest + +from tests.fixtures.accounts import add_base_pool_liquidity, mint_account +from tests.fixtures.constants import INITIAL_AMOUNT +from tests.utils.tokens import mint_for_testing + +DEPOSIT_AMOUNT = INITIAL_AMOUNT // 100 + + +@pytest.mark.only_for_token_types(1) +class TestOracle: + class TestInitialLiquidity: + @pytest.fixture(scope="module") + def initial_setup_alice( + self, + alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_balance, + initial_amounts, + pool_tokens, + underlying_tokens, + ): + with boa.env.anchor(): + mint_for_testing(alice, 1 * 10**18, None, True) + + if pool_type == 0: + mint_account(alice, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in pool_tokens: + token.approve(swap.address, 2**256 - 1) + + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) + + yield + + def test_initial_liquidity( + self, + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + pool_tokens, + metapool_token, + ): + amounts = [] + + if pool_type == 0: + for i, t in enumerate(pool_token_types): + if t != 1: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) + else: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // pool_tokens[i].exchangeRate()) + else: + if metapool_token_type == 1: + amounts = [ + DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10**18, + ] + else: + amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + + swap.add_liquidity(amounts, 0, sender=alice) + swap.add_liquidity(amounts, 0, sender=alice) + + assert swap.admin_balances(0) == 0 + assert swap.admin_balances(1) == 0 + + def test_oracles(self, alice, swap, pool_size, pool_type, pool_token_types, metapool_token_type): + assert swap._storage.oracles.get() != [0] * pool_size + + if pool_type == 0: + assert swap._immutables.asset_types == pool_token_types + else: + assert swap._immutables.asset_types == [metapool_token_type, 0, 0, 0, 0] + + def test_get_dy( + self, + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + pool_tokens, + metapool_token, + ): + amounts = [] + + if pool_type == 0: + for i, t in enumerate(pool_token_types): + if t != 1: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) + else: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // pool_tokens[i].exchangeRate()) + else: + if metapool_token_type == 1: + amounts = [ + DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10**18, + ] + else: + amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + + swap.add_liquidity(amounts, 0, sender=alice) + + if pool_type == 0: + rate_1 = 10**18 if pool_token_types[0] != 1 else pool_tokens[0].exchangeRate() + rate_2 = 10**18 if pool_token_types[1] != 1 else pool_tokens[1].exchangeRate() + + assert swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) + + else: + rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() + + assert swap.get_dy(0, 1, 10**18) == pytest.approx(rate_1, rel=1e-3) From e3fea843625469cd0fdad0f377af3dc45420d176 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Mon, 21 Aug 2023 23:52:22 +0200 Subject: [PATCH 148/337] fix last prices --- contracts/main/CurveStableSwapMetaNG.vy | 6 +++++- contracts/main/CurveStableSwapNG.vy | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c4725e17..a98ad13d 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1231,6 +1231,10 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint2 # calculate state price xp[i] = new_y last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + last_prices_packed: DynArray[uint256, MAX_COINS] = self.last_prices_packed + pp: uint256 = 0 + if new_y > 0: last_p = self._get_p(xp, amp, D1) @@ -1239,7 +1243,7 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint2 if j == N_COINS_128: break - pp: uint256 = self.last_prices_packed[j - 1] + pp = last_prices_packed[j - 1] last_p.append(pp & (2**128 - 1)) return dy, dy_0 - dy, last_p diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 9674374e..00828670 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1185,6 +1185,10 @@ def _calc_withdraw_one_coin( xp[i] = new_y last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + + last_prices_packed: DynArray[uint256, MAX_COINS] = self.last_prices_packed + pp: uint256 = 0 + if new_y > 0: last_p = self._get_p(xp, amp, D1) @@ -1193,7 +1197,7 @@ def _calc_withdraw_one_coin( if j == N_COINS_128: break - pp: uint256 = self.last_prices_packed[j - 1] + pp = last_prices_packed[j - 1] last_p.append(pp & (2**128 - 1)) return dy, dy_0 - dy, last_p From 45ceb319d36700dee82c0f18faa302be39308b8c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:58:00 +0200 Subject: [PATCH 149/337] add D oracle! --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 118 ++++++++++++++--------- tests/{test_get_p.py => test_oracles.py} | 64 +++++++++--- tests/utils/__init__.py | 16 +++ 4 files changed, 137 insertions(+), 63 deletions(-) rename tests/{test_get_p.py => test_oracles.py} (63%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c4725e17..696c99df 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -21,7 +21,7 @@ Additional features include: 1. Adds oracles based on AMM State Price (and _not_ last traded price). State prices are calculated _after_ liquidity operations, using bonding - curve math. + curve math. Also adds an exponential moving average oracle for D. 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 9674374e..6c614648 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -19,7 +19,8 @@ 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) Note: Oracle precision _must_ be 10**18. Additional features include: - 1. Adds oracles based on AMM State Price (and _not_ last traded price). + 1. Adds price oracles based on AMM State Price (and _not_ last traded price) + and a TVL oracle based on D. 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) @@ -168,7 +169,8 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] -last_prices_packed: public(DynArray[uint256, MAX_COINS]) # packing: last_price, ma_price +last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price +last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -248,9 +250,9 @@ def __init__( N_COINS_128 = convert(__n_coins, int128) for i in range(MAX_COINS): - if i == __n_coins - 1: # __n_coins == 2 + if i == __n_coins - 1: break - self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) # length of 1 maximally + self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) rate_multipliers = _rate_multipliers asset_types = _asset_types @@ -597,12 +599,15 @@ def add_liquidity( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D2: uint256 = self.get_D(xp, amp) mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) + self.upkeep_oracles(xp, amp, D2) else: mint_amount = D1 # Take the dust if there was any + # instantiate D oracle + self.last_D_packed = self.pack_prices(D1, D1) + assert mint_amount >= _min_mint_amount, "Slippage screwed you" # Mint pool tokens @@ -634,8 +639,11 @@ def remove_liquidity_one_coin( """ dy: uint256 = 0 fee: uint256 = 0 - p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + amp: uint256 = empty(uint256) + D: uint256 = empty(uint256) + + dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" self.admin_balances[i] += fee * admin_fee / FEE_DENOMINATOR @@ -646,7 +654,7 @@ def remove_liquidity_one_coin( log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) - self.save_p_from_price(p) + self.upkeep_oracles(xp, amp, D) return dy @@ -713,7 +721,7 @@ def remove_liquidity_imbalance( D2: uint256 = self.get_D_mem(rates, new_balances, amp) - self.save_p(new_balances, amp, D2) + self.upkeep_oracles(new_balances, amp, D2) total_supply: uint256 = self.total_supply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 @@ -825,7 +833,7 @@ def __exchange( xp[i] = x xp[j] = y # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) + self.upkeep_oracles(xp, amp, D) return dy @@ -1138,7 +1146,9 @@ def _calc_withdraw_one_coin( ) -> ( uint256, uint256, - DynArray[uint256, MAX_COINS] + DynArray[uint256, MAX_COINS], + uint256, + uint256 ): # First, need to calculate # * Get current D @@ -1183,20 +1193,10 @@ def _calc_withdraw_one_coin( dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + # update xp with new_y for p calculations. xp[i] = new_y - last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - if new_y > 0: - last_p = self._get_p(xp, amp, D1) - - else: - for j in range(1, MAX_COINS_128): - if j == N_COINS_128: - break - - pp: uint256 = self.last_prices_packed[j - 1] - last_p.append(pp & (2**128 - 1)) - return dy, dy_0 - dy, last_p + return dy, dy_0 - dy, xp, amp, D1 # -------------------------- AMM Price Methods ------------------------------- @@ -1242,50 +1242,63 @@ def _get_p( @internal -def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): +def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): """ - Saves current price and its EMA + @notice Upkeeps price and D oracles. """ ma_last_time: uint256 = self.ma_last_time + last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed + last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current + + spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D) + + # -------------------------- Upkeep price oracle ------------------------- for i in range(MAX_COINS): - if i == N_COINS - 1: # 1 (N_COINS is 2) + if i == N_COINS - 1: break - if last_prices[i] != 0: + if spot_price[i] != 0: # Upate packed prices ----------------- - self.last_prices_packed[i] = self.pack_prices(last_prices[i], self._ma_price(i)) + last_prices_packed_new[i] = self.pack_prices( + spot_price[i], + self._calc_moving_average(last_prices_packed_current[i]) + ) - # Update ma_last_time ------------------ - if ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + self.last_prices_packed = last_prices_packed_new + # ---------------------------- Upkeep D oracle --------------------------- -@internal -def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): - """ - Saves current price and its EMA - """ - self.save_p_from_price(self._get_p(xp, amp, D)) + last_D_packed_current: uint256 = self.last_D_packed + self.last_D_packed = self.pack_prices( + D, + self._calc_moving_average(last_D_packed_current) + ) + + # Housekeeping: Update ma_last_time ------------------ + if ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp @internal @view -def _ma_price(i: uint256) -> uint256: +def _calc_moving_average(packed_value: uint256) -> uint256: ma_last_time: uint256 = self.ma_last_time - pp: uint256 = self.last_prices_packed[i] - last_price: uint256 = pp & (2**128 - 1) - last_ema_price: uint256 = (pp >> 128) + last_spot_value: uint256 = packed_value & (2**128 - 1) + last_ema_value: uint256 = (packed_value >> 128) - if ma_last_time < block.timestamp: - alpha: uint256 = self.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) - return (last_price * (10**18 - alpha) + last_ema_price * alpha) / 10**18 + if ma_last_time < block.timestamp: # calculate new_ema_value and return that. + alpha: uint256 = self.exp( + -convert( + (block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256 + ) + ) + return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 - else: - return last_ema_price + return last_ema_value @view @@ -1321,7 +1334,14 @@ def get_p(i: uint256) -> uint256: @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - return self._ma_price(i) + return self._calc_moving_average(self.last_prices_packed[i]) + + +@external +@view +@nonreentrant('lock') +def D_oracle() -> uint256: + return self._calc_moving_average(self.last_D_packed) # ----------------------------- Math Utils ----------------------------------- @@ -1607,7 +1627,9 @@ def get_virtual_price() -> uint256: @return LP token virtual price normalized to 1e18 """ amp: uint256 = self._A() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) D: uint256 = self.get_D(xp, amp) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio diff --git a/tests/test_get_p.py b/tests/test_oracles.py similarity index 63% rename from tests/test_get_p.py rename to tests/test_oracles.py index fd5a9410..f7916b41 100644 --- a/tests/test_get_p.py +++ b/tests/test_oracles.py @@ -6,25 +6,20 @@ from boa.test import strategy from hypothesis import given, settings +from tests.utils import approx from tests.utils.tokens import mint_for_testing SETTINGS = {"max_examples": 1000, "deadline": None} pytestmark = pytest.mark.usefixtures("initial_setup") -def approx(x1: int, x2: int, precision: int, abs_precision=None): - if precision >= 1: - return True - result = False - if abs_precision is not None: - result = abs(x2 - x1) <= abs_precision - else: - abs_precision = 0 - if x2 == 0: - return abs(x1) <= abs_precision - elif x1 == 0: - return abs(x2) <= abs_precision - return result or (abs(log(x1 / x2)) <= precision) +def get_D(swap): + + _rates = [swap.stored_rates(i) for i in range(swap.N_COINS())] + _balances = swap.internal._balances() + xp = swap.internal._xp_mem(_rates, _balances) + amp = swap.internal._A() + return swap.internal.get_D(xp, amp) @given( @@ -72,7 +67,7 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): dt=strategy("uint256", min_value=0, max_value=10**6), ) @settings(**SETTINGS) -def test_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): +def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): for token in pool_tokens: if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: @@ -112,3 +107,44 @@ def test_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, d p1 = int(10**18 * w + p_amm[n] * (1 - w)) assert approx(swap.price_oracle(n), p1, 1e-5) + + +@pytest.mark.only_for_pool_type(0) +@given( + amount=strategy("uint256", min_value=1, max_value=10**5), + dt0=strategy("uint256", min_value=0, max_value=10**6), + dt=strategy("uint256", min_value=0, max_value=10**6), +) +@settings(**SETTINGS) +def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): + + for token in pool_tokens: + if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: + return # TODO: rebasing tokens that rebase downwards are causing trouble here. + + i, j = random.sample(range(swap.N_COINS()), 2) + + # calc amount in: + amount_in = amount * 10 ** (decimals[i]) + + # mint tokens for bob if he needs: + if amount_in > pool_tokens[i].balanceOf(bob): + mint_for_testing(bob, amount_in, pool_tokens[i], False) + + boa.env.time_travel(dt0) + swap.exchange(i, j, amount, 0, sender=bob) + + # check D oracle before time travel (shouldnt really change): + D0 = get_D(swap) + assert approx(swap.D_oracle(), D0, 1e-5) + + # time travel dt amount: + boa.env.time_travel(dt) + + # calculate weights based on time travelled: + w = exp(-dt / 866) + + # check: + D1 = get_D(swap) + D1 = int(D0 * w + D1 * (1 - w)) + assert approx(swap.D_oracle(), D1, 1e-5) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index e69de29b..b274793d 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -0,0 +1,16 @@ +from math import log + + +def approx(x1: int, x2: int, precision: int, abs_precision=None): + if precision >= 1: + return True + result = False + if abs_precision is not None: + result = abs(x2 - x1) <= abs_precision + else: + abs_precision = 0 + if x2 == 0: + return abs(x1) <= abs_precision + elif x1 == 0: + return abs(x2) <= abs_precision + return result or (abs(log(x1 / x2)) <= precision) From 3a64e804e45b71bb0142f52c590b3fbe099d38ab Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:43:44 +0200 Subject: [PATCH 150/337] add D oracle for meta --- contracts/main/CurveStableSwapMetaNG.vy | 121 +++++++++++++++--------- contracts/main/CurveStableSwapNG.vy | 2 +- tests/pools/test_exchange.py | 1 - tests/test_oracles.py | 11 +-- 4 files changed, 80 insertions(+), 55 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 52bc4e54..1b6bb47f 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -214,6 +214,7 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) oracles: DynArray[uint256, MAX_COINS] last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price +last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) ma_last_time: public(uint256) @@ -713,12 +714,15 @@ def add_liquidity( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D2: uint256 = math.get_D(xp, amp, N_COINS) mint_amount = total_supply * (D2 - D0) / D0 - self.save_p(xp, amp, D2) + self.upkeep_oracles(xp, amp, D2) else: mint_amount = D1 # Take the dust if there was any + # (re)instantiate D oracle if totalSupply is zero. + self.last_D_packed = self.pack_prices(D1, D1) + assert mint_amount >= _min_mint_amount, "Slippage screwed you" # Mint pool tokens @@ -750,8 +754,11 @@ def remove_liquidity_one_coin( """ dy: uint256 = 0 fee: uint256 = 0 - p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - dy, fee, p = self._calc_withdraw_one_coin(_burn_amount, i) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + amp: uint256 = empty(uint256) + D: uint256 = empty(uint256) + + dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" # fee * admin_fee / FEE_DENOMINATOR @@ -765,7 +772,7 @@ def remove_liquidity_one_coin( log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) - self.save_p_from_price(p) + self.upkeep_oracles(xp, amp, D) return dy @@ -829,7 +836,7 @@ def remove_liquidity_imbalance( D2: uint256 = self.get_D_mem(rates, new_balances, amp) - self.save_p(new_balances, amp, D2) + self.upkeep_oracles(new_balances, amp, D2) total_supply: uint256 = self.total_supply burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 @@ -938,7 +945,7 @@ def __exchange( xp[i] = x xp[j] = y # D is not changed because we did not apply a fee - self.save_p(xp, amp, D) + self.upkeep_oracles(xp, amp, D) return dy @@ -1184,7 +1191,16 @@ def get_D_mem( @view @internal -def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint256, DynArray[uint256, MAX_COINS]): +def _calc_withdraw_one_coin( + _burn_amount: uint256, + i: int128 +) -> ( + uint256, + uint256, + DynArray[uint256, MAX_COINS], + uint256, + uint256 +): # First, need to: # * Get current D @@ -1230,23 +1246,8 @@ def _calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> (uint256, uint2 # calculate state price xp[i] = new_y - last_p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - - last_prices_packed: DynArray[uint256, MAX_COINS] = self.last_prices_packed - pp: uint256 = 0 - - if new_y > 0: - last_p = self._get_p(xp, amp, D1) - - else: - for j in range(1, MAX_COINS_128): - if j == N_COINS_128: - break - pp = last_prices_packed[j - 1] - last_p.append(pp & (2**128 - 1)) - - return dy, dy_0 - dy, last_p + return dy, dy_0 - dy, xp, amp, D1 # -------------------------- AMM Price Methods ------------------------------- @@ -1260,7 +1261,7 @@ def pack_prices(p1: uint256, p2: uint256) -> uint256: @internal -@view +@pure def _get_p( xp: DynArray[uint256, MAX_COINS], amp: uint256, @@ -1285,43 +1286,59 @@ def _get_p( @internal -def save_p_from_price(last_prices: DynArray[uint256, MAX_COINS]): +def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): """ - Saves current price and its EMA + @notice Upkeeps price and D oracles. """ - if last_prices[0] != 0: + ma_last_time: uint256 = self.ma_last_time + last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed + last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current + + spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D) + + # -------------------------- Upkeep price oracle ------------------------- + + # Metapools are always 2-coin pools, so we care about idx=0 only: + if spot_price[0] != 0: # Upate packed prices ----------------- - self.last_prices_packed[0] = self.pack_prices(last_prices[0], self._ma_price()) + last_prices_packed_new[0] = self.pack_prices( + spot_price[0], + self._calc_moving_average(last_prices_packed_current[0]) + ) - # Update ma_last_time ------------------ - if self.ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + self.last_prices_packed = last_prices_packed_new + # ---------------------------- Upkeep D oracle --------------------------- -@internal -def save_p(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): - """ - Saves current price and its EMA - """ - self.save_p_from_price(self._get_p(xp, amp, D)) + last_D_packed_current: uint256 = self.last_D_packed + self.last_D_packed = self.pack_prices( + D, + self._calc_moving_average(last_D_packed_current) + ) + + # Housekeeping: Update ma_last_time ------------------ + if ma_last_time < block.timestamp: + self.ma_last_time = block.timestamp @internal @view -def _ma_price() -> uint256: +def _calc_moving_average(packed_value: uint256) -> uint256: ma_last_time: uint256 = self.ma_last_time - pp: uint256 = self.last_prices_packed[0] - last_price: uint256 = pp & (2**128 - 1) - last_ema_price: uint256 = (pp >> 128) + last_spot_value: uint256 = packed_value & (2**128 - 1) + last_ema_value: uint256 = (packed_value >> 128) - if ma_last_time < block.timestamp: - alpha: uint256 = math.exp(- convert((block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256)) - return unsafe_div(last_price * (10**18 - alpha) + last_ema_price * alpha, 10**18) + if ma_last_time < block.timestamp: # calculate new_ema_value and return that. + alpha: uint256 = math.exp( + -convert( + (block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256 + ) + ) + return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 - else: - return last_ema_price + return last_ema_value @view @@ -1345,6 +1362,8 @@ def get_p(i: uint256) -> uint256: @param i index of state price (0 for coin[1], 1 for coin[2], ...) @return uint256 The state price quoted by the AMM for coin[i+1] """ + assert i == 0 # dev: metapools do not have price oracle indices greater than 0. + amp: uint256 = self._A() xp: DynArray[uint256, MAX_COINS] = self._xp_mem( self._stored_rates(), self._balances() @@ -1357,7 +1376,15 @@ def get_p(i: uint256) -> uint256: @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - return self._ma_price() + assert i == 0 # dev: metapools do not have price oracle indices greater than 0. + return self._calc_moving_average(self.last_prices_packed[0]) + + +@external +@view +@nonreentrant('lock') +def D_oracle() -> uint256: + return self._calc_moving_average(self.last_D_packed) # ---------------------------- ERC20 Utils ----------------------------------- diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 6c614648..ac7b8958 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -605,7 +605,7 @@ def add_liquidity( mint_amount = D1 # Take the dust if there was any - # instantiate D oracle + # (re)instantiate D oracle if totalSupply is zero. self.last_D_packed = self.pack_prices(D1, D1) assert mint_amount >= _min_mint_amount, "Slippage screwed you" diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index e4149760..924bc041 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -143,7 +143,6 @@ def test_remove_liquidity( i += 1 else: for coin, amount in zip(underlying_tokens[:2], deposit_amounts): - print(coin.balanceOf(swap), coin.balanceOf(charlie), i) assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx( deposit_amounts[0] * 2, rel=1.5e-2 ) diff --git a/tests/test_oracles.py b/tests/test_oracles.py index f7916b41..366c9474 100644 --- a/tests/test_oracles.py +++ b/tests/test_oracles.py @@ -13,13 +13,13 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -def get_D(swap): +def get_D(swap, math): _rates = [swap.stored_rates(i) for i in range(swap.N_COINS())] _balances = swap.internal._balances() xp = swap.internal._xp_mem(_rates, _balances) amp = swap.internal._A() - return swap.internal.get_D(xp, amp) + return math.get_D(xp, amp, swap.N_COINS()) @given( @@ -109,14 +109,13 @@ def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, assert approx(swap.price_oracle(n), p1, 1e-5) -@pytest.mark.only_for_pool_type(0) @given( amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), ) @settings(**SETTINGS) -def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): +def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt, math_implementation): for token in pool_tokens: if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: @@ -135,7 +134,7 @@ def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, swap.exchange(i, j, amount, 0, sender=bob) # check D oracle before time travel (shouldnt really change): - D0 = get_D(swap) + D0 = get_D(swap, math_implementation) assert approx(swap.D_oracle(), D0, 1e-5) # time travel dt amount: @@ -145,6 +144,6 @@ def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, w = exp(-dt / 866) # check: - D1 = get_D(swap) + D1 = get_D(swap, math_implementation) D1 = int(D0 * w + D1 * (1 - w)) assert approx(swap.D_oracle(), D1, 1e-5) From 05c7a69d1126c931d0913ce7db9c074eebb3cb95 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:55:05 +0200 Subject: [PATCH 151/337] init deployment code --- ape-config.yaml | 60 ++ poetry.lock | 1590 +++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + scripts/deploy.py | 65 ++ scripts/deployment_utils.py | 154 ++++ scripts/simulate.py | 37 + scripts/vote_utils.py | 133 +++ 7 files changed, 1980 insertions(+), 60 deletions(-) create mode 100644 ape-config.yaml create mode 100644 scripts/deploy.py create mode 100644 scripts/deployment_utils.py create mode 100644 scripts/simulate.py create mode 100644 scripts/vote_utils.py diff --git a/ape-config.yaml b/ape-config.yaml new file mode 100644 index 00000000..adb1d441 --- /dev/null +++ b/ape-config.yaml @@ -0,0 +1,60 @@ +name: curve-stableswap-ng +contracts_folder: contracts/main/ + +plugins: + - name: vyper + - name: alchemy + - name: hardhat + - name: ledger + - name: etherscan + - name: arbitrum + - name: optimism + - name: polygon + - name: gnosis + +default_ecosystem: ethereum + +# vyper: +# evm_version: paris # enable for non PUSH0 evm networks + +hardhat: + port: auto + fork: + ethereum: + mainnet: + upstream_provider: alchemy + sepolia: + upstream_provider: alchemy + arbitrum: + mainnet: + upstream_provider: geth + +ethereum: + default_network: mainnet-fork + mainnet_fork: + transaction_acceptance_timeout: 99999999 + default_provider: hardhat + mainnet: + transaction_acceptance_timeout: 99999999 + sepolia: + transaction_acceptance_timeout: 99999999 + +arbitrum: + default_network: mainnet-fork + mainnet_fork: + transaction_acceptance_timeout: 99999999 + default_provider: hardhat + mainnet: + transaction_acceptance_timeout: 99999999 + +geth: + ethereum: + mainnet: + uri: http://localhost:9090 + arbitrum: + mainnet: + uri: https://arb-mainnet.g.alchemy.com/v2/{some_key} + +test: + mnemonic: test test test test test test test test test test test junk + number_of_accounts: 5 diff --git a/poetry.lock b/poetry.lock index fccd71c7..4e0f7c2e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,127 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "appnope" version = "0.1.3" @@ -28,6 +150,17 @@ six = "*" [package.extras] test = ["astroid", "pytest"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + [[package]] name = "attrs" version = "23.1.0" @@ -57,6 +190,18 @@ files = [ {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] +[[package]] +name = "base58" +version = "1.0.3" +description = "Base58 and Base58Check implementation" +optional = false +python-versions = "*" +files = [ + {file = "base58-1.0.3-py2-none-any.whl", hash = "sha256:1e42993c0628ed4f898c03b522b26af78fb05115732549b21a028bc4633d19ab"}, + {file = "base58-1.0.3-py3-none-any.whl", hash = "sha256:6aa0553e477478993588303c54659d15e3c17ae062508c854a8b752d07c716bd"}, + {file = "base58-1.0.3.tar.gz", hash = "sha256:9a793c599979c497800eb414c852b80866f28daaed5494703fc129592cc83e60"}, +] + [[package]] name = "bitarray" version = "2.7.6" @@ -466,13 +611,13 @@ rapidfuzz = ">=2.2.0,<3.0.0" [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -489,6 +634,20 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +optional = false +python-versions = "*" +files = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + [[package]] name = "crashtest" version = "0.4.1" @@ -676,6 +835,23 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "distlib" version = "0.3.6" @@ -838,6 +1014,56 @@ doc = ["Sphinx (>=1.6.5,<5)", "jinja2 (>=3.0.0,<3.1.0)", "sphinx-rtd-theme (>=0. lint = ["black (>=22,<23)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)"] test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.25.0)"] +[[package]] +name = "eth-ape" +version = "0.6.18" +description = "Ape Ethereum Framework" +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "eth-ape-0.6.18.tar.gz", hash = "sha256:ebc04365d830ab06a1595a22e8f9c263769a57a9eb1dfc7f4bec2db353ccbf0a"}, + {file = "eth_ape-0.6.18-py3-none-any.whl", hash = "sha256:73e7533c64e23eaba33ecb48b3fbd9e423ffd088050cacb9c551fe66c1ceccf4"}, +] + +[package.dependencies] +click = ">=8.1.6,<9" +eip712 = ">=0.2.1,<0.3" +eth-abi = ">=4.1.0,<5" +eth-account = ">=0.8,<0.9" +eth-typing = ">=3.4,<4" +eth-utils = ">=2.2.0,<3" +ethpm-types = ">=0.5.3,<0.6" +evm-trace = ">=0.1.0a22" +hexbytes = ">=0.2.3,<1" +ijson = ">=3.1.4,<4" +importlib-metadata = "*" +ipython = ">=8.5.0,<9" +lazyasd = ">=0.1.4" +packaging = ">=23.0,<24" +pandas = ">=1.3.0,<2" +pluggy = ">=0.12,<2" +py-geth = ">=3.13.0,<4" +pydantic = ">=1.10.8,<2" +PyGithub = ">=1.59,<2" +pytest = ">=6.0,<8.0" +python-dateutil = ">=2.8.2,<3" +PyYAML = ">=5.0,<7" +requests = ">=2.28.1,<3" +rich = ">=12.5.1,<13" +SQLAlchemy = ">=1.4.35" +tqdm = ">=4.62.3,<5.0" +traitlets = ">=5.3.0" +watchdog = ">=3.0,<4" +web3 = {version = ">=6.7.0,<7", extras = ["tester"]} + +[package.extras] +dev = ["Sphinx (>=6.1.3,<7)", "black (>=23.7.0,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.4.1,<2)", "myst-parser (>=1.0.0,<2)", "pandas-stubs (==1.2.0.62)", "pre-commit", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine (==3.8.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools", "wheel"] +doc = ["Sphinx (>=6.1.3,<7)", "myst-parser (>=1.0.0,<2)", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] +lint = ["black (>=23.7.0,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.4.1,<2)", "pandas-stubs (==1.2.0.62)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools"] +recommended-plugins = ["ape-alchemy", "ape-ens", "ape-etherscan", "ape-foundry", "ape-hardhat", "ape-infura", "ape-solidity", "ape-template", "ape-tokens", "ape-vyper"] +release = ["setuptools", "twine (==3.8.0)", "wheel"] +test = ["hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-xdist"] + [[package]] name = "eth-bloom" version = "2.0.0" @@ -871,6 +1097,7 @@ files = [ [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} +safe-pysha3 = {version = ">=1.0.0", optional = true, markers = "python_version >= \"3.9\" and extra == \"pysha3\""} [package.extras] dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] @@ -963,6 +1190,38 @@ safe-pysha3 = {version = ">=1.0.3,<2.0.0", markers = "python_version >= \"3.9\"" [package.extras] hypothesis = ["hypothesis (>=6.58.0,<7.0.0)"] +[[package]] +name = "eth-tester" +version = "0.9.1b1" +description = "Tools for testing Ethereum applications." +optional = false +python-versions = ">=3.6.8,<4" +files = [ + {file = "eth-tester-0.9.1b1.tar.gz", hash = "sha256:b9cbc93d0b17a6e8acbb52294dad214ee223cf88209fa5be66ead353937d274c"}, + {file = "eth_tester-0.9.1b1-py3-none-any.whl", hash = "sha256:0e4367d99ae242efdb8c1d18ed99d1ff3f03149abb0a4c2427bc6d333ebef13b"}, +] + +[package.dependencies] +eth-abi = ">=3.0.1" +eth-account = ">=0.6.0" +eth-hash = [ + {version = ">=0.1.4,<1.0.0", extras = ["pysha3"], optional = true, markers = "implementation_name == \"cpython\" and extra == \"py-evm\""}, + {version = ">=0.1.4,<1.0.0", extras = ["pycryptodome"], optional = true, markers = "implementation_name == \"pypy\" and extra == \"py-evm\""}, +] +eth-keys = ">=0.4.0,<0.5.0" +eth-utils = ">=2.0.0,<3.0.0" +py-evm = {version = "0.7.0a4", optional = true, markers = "extra == \"py-evm\""} +rlp = ">=3.0.0,<4" +semantic-version = ">=2.6.0,<3.0.0" + +[package.extras] +dev = ["black (>=22,<23)", "bumpversion (>=0.5.3,<1.0.0)", "eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "flake8 (>=3.5.0,<4.0.0)", "py-evm (==0.7.0a4)", "pytest (>=7.0.0)", "pytest-xdist (>=2.0.0,<3)", "towncrier (>=21,<22)", "tox (>=2.9.1,<3.0.0)", "wheel (>=0.30.0,<1.0.0)"] +docs = ["towncrier (>=21,<22)"] +lint = ["black (>=22,<23)", "flake8 (>=3.5.0,<4.0.0)"] +py-evm = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "py-evm (==0.7.0a4)"] +pyevm = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "py-evm (==0.7.0a4)"] +test = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.0.0,<3)"] + [[package]] name = "eth-typing" version = "3.4.0" @@ -982,13 +1241,13 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "2.1.1" +version = "2.2.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" optional = false python-versions = ">=3.7,<4" files = [ - {file = "eth-utils-2.1.1.tar.gz", hash = "sha256:7cccfb0b0749431d0d001e327e9a7289bf07308316a73850ae3895020e5682f4"}, - {file = "eth_utils-2.1.1-py3-none-any.whl", hash = "sha256:4938ab742f91cdf19bae024261af090664f63ccf83bdb1213e7146c14209e899"}, + {file = "eth-utils-2.2.0.tar.gz", hash = "sha256:7f1a9e10400ee332432a778c321f446abaedb8f538df550e7c9964f446f7e265"}, + {file = "eth_utils-2.2.0-py3-none-any.whl", hash = "sha256:d6e107d522f83adff31237a95bdcc329ac0819a3ac698fe43c8a56fd80813eab"}, ] [package.dependencies] @@ -1003,6 +1262,55 @@ doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] +[[package]] +name = "ethpm-types" +version = "0.5.4" +description = "ethpm_types: Implementation of EIP-2678" +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "ethpm-types-0.5.4.tar.gz", hash = "sha256:93e393583bf7271b312430c9ec864bec07a78794d922875d44fe3240bf536f06"}, + {file = "ethpm_types-0.5.4-py3-none-any.whl", hash = "sha256:6d18b5e37f77d6d5326ee6e5d87caecf9ccb5cba2efd978e22dac3a945b7b169"}, +] + +[package.dependencies] +eth-utils = ">=2.1.0,<3" +hexbytes = ">=0.3.0,<1" +py-cid = ">=0.3.0,<0.4" +pydantic = ">=1.10.7,<2" +requests = ">=2.29.0,<3" + +[package.extras] +dev = ["IPython", "PyGithub (>=1.54,<2.0)", "black (>=23.3.0,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.0.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<5.11)", "mypy (>=0.991,<1)", "pre-commit", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-requests", "types-setuptools", "wheel"] +doc = ["Sphinx (>=4.4.0,<5.0)", "myst-parser (>=0.17.0,<0.18)", "sphinx-click (>=3.1.0,<4.0)", "sphinx-rtd-theme (>=1.0.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] +lint = ["black (>=23.3.0,<24)", "flake8 (>=6.0.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "isort (>=5.10.1,<5.11)", "mypy (>=0.991,<1)", "types-requests", "types-setuptools"] +release = ["setuptools", "twine", "wheel"] +test = ["PyGithub (>=1.54,<2.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "evm-trace" +version = "0.1.0a22" +description = "evm-trace: Ethereum Virtual Machine transaction tracing tool" +optional = false +python-versions = ">=3.8,<4" +files = [ + {file = "evm-trace-0.1.0a22.tar.gz", hash = "sha256:5a1bc4ba6024b6311b757a130ee2a59741224bdc05186a8f768798cbcf40d6bc"}, + {file = "evm_trace-0.1.0a22-py3-none-any.whl", hash = "sha256:f4db4ba993289c46d059511f1f4be1cae8452900704d8581b0b83f0bbd9624d1"}, +] + +[package.dependencies] +eth-utils = ">=2.1,<3" +ethpm-types = ">=0.5.0,<0.6" +msgspec = ">=0.8" +py-evm = ">=0.7.0a3,<0.8" +pydantic = ">=1.10.1,<2" + +[package.extras] +dev = ["IPython", "black (>=23.3.0,<24)", "commitizen", "eth-hash[pysha3]", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.2.0,<7.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=0.991,<1)", "pre-commit", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-setuptools", "wheel"] +lint = ["black (>=23.3.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=0.991,<1)", "types-setuptools"] +release = ["setuptools", "twine", "wheel"] +test = ["eth-hash[pysha3]", "hypothesis (>=6.2.0,<7.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] + [[package]] name = "exceptiongroup" version = "1.1.1" @@ -1091,6 +1399,149 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + [[package]] name = "hexbytes" version = "0.3.1" @@ -1186,6 +1637,93 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "ijson" +version = "3.2.3" +description = "Iterative JSON parser with standard Python iterator interfaces" +optional = false +python-versions = "*" +files = [ + {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a4ae076bf97b0430e4e16c9cb635a6b773904aec45ed8dcbc9b17211b8569ba"}, + {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cfced0a6ec85916eb8c8e22415b7267ae118eaff2a860c42d2cc1261711d0d31"}, + {file = "ijson-3.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b9d1141cfd1e6d6643aa0b4876730d0d28371815ce846d2e4e84a2d4f471cf3"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0a27db6454edd6013d40a956d008361aac5bff375a9c04ab11fc8c214250b5"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0d526ccb335c3c13063c273637d8611f32970603dfb182177b232d01f14c23"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:545a30b3659df2a3481593d30d60491d1594bc8005f99600e1bba647bb44cbb5"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9680e37a10fedb3eab24a4a7e749d8a73f26f1a4c901430e7aa81b5da15f7307"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2a80c0bb1053055d1599e44dc1396f713e8b3407000e6390add72d49633ff3bb"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f05ed49f434ce396ddcf99e9fd98245328e99f991283850c309f5e3182211a79"}, + {file = "ijson-3.2.3-cp310-cp310-win32.whl", hash = "sha256:b4eb2304573c9fdf448d3fa4a4fdcb727b93002b5c5c56c14a5ffbbc39f64ae4"}, + {file = "ijson-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:923131f5153c70936e8bd2dd9dcfcff43c67a3d1c789e9c96724747423c173eb"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:904f77dd3d87736ff668884fe5197a184748eb0c3e302ded61706501d0327465"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0974444c1f416e19de1e9f567a4560890095e71e81623c509feff642114c1e53"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1a4b8eb69b6d7b4e94170aa991efad75ba156b05f0de2a6cd84f991def12ff9"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d052417fd7ce2221114f8d3b58f05a83c1a2b6b99cafe0b86ac9ed5e2fc889df"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b8064a85ec1b0beda7dd028e887f7112670d574db606f68006c72dd0bb0e0e2"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaac293853f1342a8d2a45ac1f723c860f700860e7743fb97f7b76356df883a8"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6c32c18a934c1dc8917455b0ce478fd7a26c50c364bd52c5a4fb0fc6bb516af7"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:713a919e0220ac44dab12b5fed74f9130f3480e55e90f9d80f58de129ea24f83"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a3a6a2fbbe7550ffe52d151cf76065e6b89cfb3e9d0463e49a7e322a25d0426"}, + {file = "ijson-3.2.3-cp311-cp311-win32.whl", hash = "sha256:6a4db2f7fb9acfb855c9ae1aae602e4648dd1f88804a0d5cfb78c3639bcf156c"}, + {file = "ijson-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccd6be56335cbb845f3d3021b1766299c056c70c4c9165fb2fbe2d62258bae3f"}, + {file = "ijson-3.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eeb286639649fb6bed37997a5e30eefcacddac79476d24128348ec890b2a0ccb"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:396338a655fb9af4ac59dd09c189885b51fa0eefc84d35408662031023c110d1"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e0243d166d11a2a47c17c7e885debf3b19ed136be2af1f5d1c34212850236ac"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85afdb3f3a5d0011584d4fa8e6dccc5936be51c27e84cd2882fe904ca3bd04c5"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4fc35d569eff3afa76bfecf533f818ecb9390105be257f3f83c03204661ace70"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:455d7d3b7a6aacfb8ab1ebcaf697eedf5be66e044eac32508fccdc633d995f0e"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c63f3d57dbbac56cead05b12b81e8e1e259f14ce7f233a8cbe7fa0996733b628"}, + {file = "ijson-3.2.3-cp36-cp36m-win32.whl", hash = "sha256:a4d7fe3629de3ecb088bff6dfe25f77be3e8261ed53d5e244717e266f8544305"}, + {file = "ijson-3.2.3-cp36-cp36m-win_amd64.whl", hash = "sha256:96190d59f015b5a2af388a98446e411f58ecc6a93934e036daa75f75d02386a0"}, + {file = "ijson-3.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35194e0b8a2bda12b4096e2e792efa5d4801a0abb950c48ade351d479cd22ba5"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1053fb5f0b010ee76ca515e6af36b50d26c1728ad46be12f1f147a835341083"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:211124cff9d9d139dd0dfced356f1472860352c055d2481459038b8205d7d742"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92dc4d48e9f6a271292d6079e9fcdce33c83d1acf11e6e12696fb05c5889fe74"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3dcc33ee56f92a77f48776014ddb47af67c33dda361e84371153c4f1ed4434e1"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98c6799925a5d1988da4cd68879b8eeab52c6e029acc45e03abb7921a4715c4b"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4252e48c95cd8ceefc2caade310559ab61c37d82dfa045928ed05328eb5b5f65"}, + {file = "ijson-3.2.3-cp37-cp37m-win32.whl", hash = "sha256:644f4f03349ff2731fd515afd1c91b9e439e90c9f8c28292251834154edbffca"}, + {file = "ijson-3.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ba33c764afa9ecef62801ba7ac0319268a7526f50f7601370d9f8f04e77fc02b"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4b2ec8c2a3f1742cbd5f36b65e192028e541b5fd8c7fd97c1fc0ca6c427c704a"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dc357da4b4ebd8903e77dbcc3ce0555ee29ebe0747c3c7f56adda423df8ec89"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bcc51c84bb220ac330122468fe526a7777faa6464e3b04c15b476761beea424f"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8d54b624629f9903005c58d9321a036c72f5c212701bbb93d1a520ecd15e370"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6ea7c7e3ec44742e867c72fd750c6a1e35b112f88a917615332c4476e718d40"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:916acdc5e504f8b66c3e287ada5d4b39a3275fc1f2013c4b05d1ab9933671a6c"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81815b4184b85ce124bfc4c446d5f5e5e643fc119771c5916f035220ada29974"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b49fd5fe1cd9c1c8caf6c59f82b08117dd6bea2ec45b641594e25948f48f4169"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:86b3c91fdcb8ffb30556c9669930f02b7642de58ca2987845b04f0d7fe46d9a8"}, + {file = "ijson-3.2.3-cp38-cp38-win32.whl", hash = "sha256:a729b0c8fb935481afe3cf7e0dadd0da3a69cc7f145dbab8502e2f1e01d85a7c"}, + {file = "ijson-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:d34e049992d8a46922f96483e96b32ac4c9cffd01a5c33a928e70a283710cd58"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9c2a12dcdb6fa28f333bf10b3a0f80ec70bc45280d8435be7e19696fab2bc706"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1844c5b57da21466f255a0aeddf89049e730d7f3dfc4d750f0e65c36e6a61a7c"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ec3e5ff2515f1c40ef6a94983158e172f004cd643b9e4b5302017139b6c96e4"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46bafb1b9959872a1f946f8dd9c6f1a30a970fc05b7bfae8579da3f1f988e598"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab4db9fee0138b60e31b3c02fff8a4c28d7b152040553b6a91b60354aebd4b02"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4bc87e69d1997c6a55fff5ee2af878720801ff6ab1fb3b7f94adda050651e37"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e9fd906f0c38e9f0bfd5365e1bed98d649f506721f76bb1a9baa5d7374f26f19"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e84d27d1acb60d9102728d06b9650e5b7e5cb0631bd6e3dfadba8fb6a80d6c2f"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2cc04fc0a22bb945cd179f614845c8b5106c0b3939ee0d84ce67c7a61ac1a936"}, + {file = "ijson-3.2.3-cp39-cp39-win32.whl", hash = "sha256:e641814793a037175f7ec1b717ebb68f26d89d82cfd66f36e588f32d7e488d5f"}, + {file = "ijson-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:6bd3e7e91d031f1e8cea7ce53f704ab74e61e505e8072467e092172422728b22"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06f9707da06a19b01013f8c65bf67db523662a9b4a4ff027e946e66c261f17f0"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be8495f7c13fa1f622a2c6b64e79ac63965b89caf664cc4e701c335c652d15f2"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7596b42f38c3dcf9d434dddd50f46aeb28e96f891444c2b4b1266304a19a2c09"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbac4e9609a1086bbad075beb2ceec486a3b138604e12d2059a33ce2cba93051"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:db2d6341f9cb538253e7fe23311d59252f124f47165221d3c06a7ed667ecd595"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fa8b98be298efbb2588f883f9953113d8a0023ab39abe77fe734b71b46b1220a"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:674e585361c702fad050ab4c153fd168dc30f5980ef42b64400bc84d194e662d"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd12e42b9cb9c0166559a3ffa276b4f9fc9d5b4c304e5a13668642d34b48b634"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d31e0d771d82def80cd4663a66de277c3b44ba82cd48f630526b52f74663c639"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ce4c70c23521179d6da842bb9bc2e36bb9fad1e0187e35423ff0f282890c9ca"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39f551a6fbeed4433c85269c7c8778e2aaea2501d7ebcb65b38f556030642c17"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b14d322fec0de7af16f3ef920bf282f0dd747200b69e0b9628117f381b7775b"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7851a341429b12d4527ca507097c959659baf5106c7074d15c17c387719ffbcd"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3bf1b42191b5cc9b6441552fdcb3b583594cb6b19e90d1578b7cbcf80d0fae"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6f662dc44362a53af3084d3765bb01cd7b4734d1f484a6095cad4cb0cbfe5374"}, + {file = "ijson-3.2.3.tar.gz", hash = "sha256:10294e9bf89cb713da05bc4790bdff616610432db561964827074898e174f917"}, +] + [[package]] name = "importlib-metadata" version = "6.7.0" @@ -1392,6 +1930,16 @@ atomic-cache = ["atomicwrites"] nearley = ["js2py"] regex = ["regex"] +[[package]] +name = "lazyasd" +version = "0.1.4" +description = "Lazy & self-destructive tools for speeding up module imports" +optional = false +python-versions = "*" +files = [ + {file = "lazyasd-0.1.4.tar.gz", hash = "sha256:a3196f05cff27f952ad05767e5735fd564b4ea4e89b23f5ea1887229c3db145b"}, +] + [[package]] name = "lockfile" version = "0.12.2" @@ -1516,30 +2064,6 @@ pathspec = ">=0.9.0" [package.extras] dev = ["black (>=22.6.0)", "bump2version (>=1.0.0)", "coverage (>=6.0.0)", "flake8 (==5.0.4)", "mypy (>=0.900)", "mypy-extensions (==0.4.3)", "pylint (==2.14.5)", "pytest (>=6.0.0)", "pytest-cov (>=3.0.0)"] -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - [[package]] name = "matplotlib-inline" version = "0.1.6" @@ -1565,17 +2089,6 @@ files = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "more-itertools" version = "9.1.0" @@ -1587,6 +2100,16 @@ files = [ {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, ] +[[package]] +name = "morphys" +version = "1.0" +description = "Smart conversions between unicode and bytes types for common cases" +optional = false +python-versions = "*" +files = [ + {file = "morphys-1.0-py2.py3-none-any.whl", hash = "sha256:76d6dbaa4d65f597e59d332c81da786d83e4669387b9b2a750cfec74e7beec20"}, +] + [[package]] name = "msgpack" version = "1.0.5" @@ -1659,14 +2182,143 @@ files = [ {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, ] +[[package]] +name = "msgspec" +version = "0.18.1" +description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgspec-0.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:262e5f1a981644f5e9b28a984d6df238eac0ed2c37d788f40abaf10d380b0424"}, + {file = "msgspec-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3f4a7d5897984f59baf51976682f52f4d2eff88aa64eec8b7f5b80b7a2b6dc5"}, + {file = "msgspec-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d21f0da90d1f3e7f65123d375c2b590bfbe3a014920ea812e1a022027b60d3"}, + {file = "msgspec-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebb71f51ca2f62d0d1cf9585f763e3d9fd8a85a0f00d682112916532964692ae"}, + {file = "msgspec-0.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:52bb422d2b2e80e86d72439edb1a372ff016c0c5d9f44d277b220511486b3f9a"}, + {file = "msgspec-0.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:119ff9d5412eccf53f0d7ab43b587a58f6f806a40ae6b14fd8140173b1cc0285"}, + {file = "msgspec-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:36da63a8f64292fff7c814bd3cbbba669f0fa5068c149b4a386dba662ace5621"}, + {file = "msgspec-0.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b132ae2691623b3fbee730604671da3bf57ca97d648f1a46a35ea09c6f490fcf"}, + {file = "msgspec-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:944af5f9ec66de8e21b30ccafdab3e87be11d757ca9304c01e3b2efc373f3293"}, + {file = "msgspec-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e96b71e98728830ea3d20acf65969ba7059dfc9e32581039f05e88b8461d98b"}, + {file = "msgspec-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:914ab6407fdce1bc795583ba0428f49baf6eedd40df117200ec8fe7666ca2ec4"}, + {file = "msgspec-0.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13980627b9ba28fac2e051757ef5edbe0bebfef411d648e58d2100b86c5eca03"}, + {file = "msgspec-0.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6bd460e2057a6d9ca6d0e2b848a441c6e30977012d1fca43e4bc1702c9abca5"}, + {file = "msgspec-0.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:c63f9dc8c33ca56903d7957ee7a5d0b3ece6b345b8b10f44b226787a4410c8e1"}, + {file = "msgspec-0.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a88d2fb82eb59de0f0f365a27dcea2020440883770b99353e0a3e0aa8ef552a"}, + {file = "msgspec-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f936f6ea351c2e8c0d53875a6a71d9887603e3faadbe380172d22283e011f1b3"}, + {file = "msgspec-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597324c72b504f04fb283078b748e664f7f7fc5337bb493e0996511bab895e7e"}, + {file = "msgspec-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94eca90ac47a24cb07b3702c863a6b0881830ea02de7ac58a00f778b4b7762b"}, + {file = "msgspec-0.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c256b21baa1336d651aa9955d69d3f8d0e971d78e0e75fd28a89742af7dcff65"}, + {file = "msgspec-0.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9721948182d471c4f1655634f1970628a7321ec58a2cfb89d35af78c2fc2ebfa"}, + {file = "msgspec-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:f01fe87b592185a11091206a745fdcba5b40d4c4342d1089959df49f18d6a700"}, + {file = "msgspec-0.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:966ee615b8227c208276863c4269a01dfe8c3b2538b8e1ee33bb01010fe7a3af"}, + {file = "msgspec-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d94555a79013c100b1345e0a5a6d36bfa915e74831e42de3a97cb81313426ea7"}, + {file = "msgspec-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c63a0f49540e2a888acec8c154066b6ef985813ce27132eb38e1f0c7f49df27"}, + {file = "msgspec-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0784a23d1b5b8a6fe4c9f5b9c1779adc5bff4de0fe388545dc2a4f1b5b90e546"}, + {file = "msgspec-0.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4bd273e373e220437a22a5ad83a90e3dd648c4e625f48e42e172026de77a3038"}, + {file = "msgspec-0.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a0f5070d77658ac953f63114fbaafc6ec967e9e61993ff70cc2432e938a3cf3e"}, + {file = "msgspec-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:fe22658000c165be1055c8df3f7ecc3db85be838d47d1ce1e0526b10747bdf0f"}, + {file = "msgspec-0.18.1.tar.gz", hash = "sha256:a7d837e370cfe5afb941e9c922dbdbee9c854b21bafdebaf068bdf15c43ec21d"}, +] + +[package.extras] +dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"] +doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] +test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"] +toml = ["tomli", "tomli-w"] +yaml = ["pyyaml"] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + [[package]] name = "mypy-extensions" -version = "0.4.4" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=2.7" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] @@ -1683,6 +2335,40 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "numpy" +version = "1.25.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, +] + [[package]] name = "packaging" version = "23.1" @@ -1694,6 +2380,53 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pandas" +version = "1.5.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, + {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, + {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, + {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, + {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, + {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, + {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, + {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + [[package]] name = "parsimonious" version = "0.9.0" @@ -1920,6 +2653,28 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "protobuf" +version = "4.24.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.1-cp310-abi3-win32.whl", hash = "sha256:d414199ca605eeb498adc4d2ba82aedc0379dca4a7c364ff9bc9a179aa28e71b"}, + {file = "protobuf-4.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:5906c5e79ff50fe38b2d49d37db5874e3c8010826f2362f79996d83128a8ed9b"}, + {file = "protobuf-4.24.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:970c701ee16788d74f3de20938520d7a0aebc7e4fff37096a48804c80d2908cf"}, + {file = "protobuf-4.24.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc361148e902949dcb953bbcb148c99fe8f8854291ad01107e4120361849fd0e"}, + {file = "protobuf-4.24.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5d32363d14aca6e5c9e9d5918ad8fb65b091b6df66740ae9de50ac3916055e43"}, + {file = "protobuf-4.24.1-cp37-cp37m-win32.whl", hash = "sha256:df015c47d6855b8efa0b9be706c70bf7f050a4d5ac6d37fb043fbd95157a0e25"}, + {file = "protobuf-4.24.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d4af4fd9e9418e819be30f8df2a16e72fbad546a7576ac7f3653be92a6966d30"}, + {file = "protobuf-4.24.1-cp38-cp38-win32.whl", hash = "sha256:302e8752c760549ed4c7a508abc86b25d46553c81989343782809e1a062a2ef9"}, + {file = "protobuf-4.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:06437f0d4bb0d5f29e3d392aba69600188d4be5ad1e0a3370e581a9bf75a3081"}, + {file = "protobuf-4.24.1-cp39-cp39-win32.whl", hash = "sha256:0b2b224e9541fe9f046dd7317d05f08769c332b7e4c54d93c7f0f372dedb0b1a"}, + {file = "protobuf-4.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd39b9094a4cc003a1f911b847ab379f89059f478c0b611ba1215053e295132e"}, + {file = "protobuf-4.24.1-py3-none-any.whl", hash = "sha256:55dd644adc27d2a624339332755fe077c7f26971045b469ebb9732a69ce1f2ca"}, + {file = "protobuf-4.24.1.tar.gz", hash = "sha256:44837a5ed9c9418ad5d502f89f28ba102e9cd172b6668bc813f21716f9273348"}, +] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -1956,6 +2711,24 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +[[package]] +name = "py-cid" +version = "0.3.0" +description = "Self-describing content-addressed identifiers for distributed systems" +optional = false +python-versions = "*" +files = [ + {file = "py-cid-0.3.0.tar.gz", hash = "sha256:22f432cc6fb68d12a9c35dbdc92c95484fc49e31dfcb9e0efb0082233c5394e3"}, + {file = "py_cid-0.3.0-py2.py3-none-any.whl", hash = "sha256:7c48a6ee0bc50fd114d4b24849cd689a31d3ad5bdf8fa073bf68f846fd58c5da"}, +] + +[package.dependencies] +base58 = ">=1.0.2,<2.0" +morphys = ">=1.0,<2.0" +py-multibase = ">=1.0.0,<2.0.0" +py-multicodec = "<0.3.0" +py-multihash = ">=0.2.0,<1.0.0" + [[package]] name = "py-ecc" version = "6.0.0" @@ -1980,13 +2753,13 @@ test = ["pytest (==6.2.5)", "pytest-xdist (==1.26.0)"] [[package]] name = "py-evm" -version = "0.7.0a3" +version = "0.7.0a4" description = "Python implementation of the Ethereum Virtual Machine" optional = false python-versions = "*" files = [ - {file = "py-evm-0.7.0a3.tar.gz", hash = "sha256:afc845a5c79a592da4d2785dc4c1522a757b3f9dcebfca9da1f9116aa1a5b2ad"}, - {file = "py_evm-0.7.0a3-py3-none-any.whl", hash = "sha256:d9e53393fc8cab155d024f335100a257d1c524f0ebd66919cf97d4b54585af1a"}, + {file = "py-evm-0.7.0a4.tar.gz", hash = "sha256:d40b6ac950485111dc7ad7bd29e3f61e00d5f81dc919e8c2b3afca30f228dc05"}, + {file = "py_evm-0.7.0a4-py3-none-any.whl", hash = "sha256:1bf7b293faa70c03727358ae3e5cb0abf7282391461d9b52b82decd6ed18c2f7"}, ] [package.dependencies] @@ -1996,7 +2769,7 @@ eth-keys = ">=0.4.0,<0.5.0" eth-typing = ">=3.3.0,<4.0.0" eth-utils = ">=2.0.0,<3.0.0" lru-dict = ">=1.1.6" -mypy-extensions = ">=0.4.1,<1.0.0" +mypy-extensions = ">=1.0.0" py-ecc = ">=1.4.7,<7.0.0" pyethash = ">=0.1.27,<1.0.0" rlp = ">=3,<4" @@ -2004,13 +2777,82 @@ trie = ">=2.0.0,<3" [package.extras] benchmark = ["termcolor (>=1.1.0,<2.0.0)", "web3 (>=4.1.0,<5.0.0)"] -dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==0.971)", "mypy-extensions (>=0.4.1,<1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pyethash (>=0.1.27,<1.0.0)", "pysha3 (>=1.0.0,<2.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] +dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==1.4.0)", "mypy-extensions (>=1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pyethash (>=0.1.27,<1.0.0)", "pysha3 (>=1.0.0,<2.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] docs = ["Sphinx (>=1.5.5,<2)", "jinja2 (>=3.0.0,<3.1.0)", "py-evm (>=0.2.0-a.14)", "pysha3 (>=1.0.0,<2.0.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)"] -eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=0.4.1,<1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "pyethash (>=0.1.27,<1.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] +eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "pyethash (>=0.1.27,<1.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] eth-extra = ["blake2b-py (>=0.1.4,<0.2)", "coincurve (>=13.0.0,<14.0.0)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "plyvel (>=1.2.0,<2)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "types-setuptools"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==1.4.0)", "pydocstyle (>=6.0.0)", "types-setuptools"] test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)"] +[[package]] +name = "py-geth" +version = "3.13.0" +description = "py-geth: Run Go-Ethereum as a subprocess" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "py-geth-3.13.0.tar.gz", hash = "sha256:f3563e2de8e78599cb9c69ee5bf3bded858ac6cf59891a04177f2353c425fdb7"}, + {file = "py_geth-3.13.0-py3-none-any.whl", hash = "sha256:1eb9c1d05b51133a6961889ec508cdcb19d24d32888704c4e034cae86a3accad"}, +] + +[package.dependencies] +semantic-version = ">=2.6.0" + +[package.extras] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flaky (>=3.2.0)", "importlib-metadata (<5)", "ipython", "isort (>=5.10.1)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "requests (>=2.20)", "towncrier (>=21,<22)", "tox (>=3.28.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +lint = ["black (>=23)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "importlib-metadata (<5)", "isort (>=5.10.1)"] +test = ["flaky (>=3.2.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "py-multibase" +version = "1.0.3" +description = "Multibase implementation for Python" +optional = false +python-versions = "*" +files = [ + {file = "py-multibase-1.0.3.tar.gz", hash = "sha256:d28a20efcbb61eec28f55827a0bf329c7cea80fffd933aecaea6ae8431267fe4"}, + {file = "py_multibase-1.0.3-py2.py3-none-any.whl", hash = "sha256:2677c1fafcc0ae15ddb9c7f444c5becc2530b3889124fd4fa2959ddfefb8c15b"}, +] + +[package.dependencies] +morphys = ">=1.0,<2.0" +python-baseconv = ">=1.2.0,<2.0" +six = ">=1.10.0,<2.0" + +[[package]] +name = "py-multicodec" +version = "0.2.1" +description = "Multicodec implementation in Python" +optional = false +python-versions = "*" +files = [ + {file = "py-multicodec-0.2.1.tar.gz", hash = "sha256:83021ffe8c0e272d19b5b86bc5b39efa67c8e9f4735ce6cafdbc1ace767ec647"}, + {file = "py_multicodec-0.2.1-py2.py3-none-any.whl", hash = "sha256:55b6bb53088a63e56c434cb11b29795e8805652bac43d50a8f2a9bcf5ca84e1f"}, +] + +[package.dependencies] +morphys = ">=1.0,<2.0" +six = ">=1.10.0,<2.0" +varint = ">=1.0.2,<2.0.0" + +[[package]] +name = "py-multihash" +version = "0.2.3" +description = "Multihash implementation in Python" +optional = false +python-versions = "*" +files = [ + {file = "py-multihash-0.2.3.tar.gz", hash = "sha256:f0ade4de820afdc4b4aaa40464ec86c9da5cae3a4578cda2daab4b0eb7e5b18d"}, + {file = "py_multihash-0.2.3-py2.py3-none-any.whl", hash = "sha256:a0602c99093587dfbf1634e2e8c7726de39374b0d68587a36093b4c237af6969"}, +] + +[package.dependencies] +base58 = ">=1.0.2,<2.0" +morphys = ">=1.0,<2.0" +six = ">=1.10.0,<2.0" +varint = ">=1.0.2,<2.0" + [[package]] name = "pycodestyle" version = "2.8.0" @@ -2074,6 +2916,58 @@ files = [ {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, ] +[[package]] +name = "pydantic" +version = "1.10.12" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyethash" version = "0.1.27" @@ -2095,6 +2989,23 @@ files = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] +[[package]] +name = "pygithub" +version = "1.59.1" +description = "Use the full Github API v3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyGithub-1.59.1-py3-none-any.whl", hash = "sha256:3d87a822e6c868142f0c2c4bf16cce4696b5a7a4d142a7bd160e1bdf75bc54a9"}, + {file = "PyGithub-1.59.1.tar.gz", hash = "sha256:c44e3a121c15bf9d3a5cc98d94c9a047a5132a9b01d22264627f58ade9ddc217"}, +] + +[package.dependencies] +deprecated = "*" +pyjwt = {version = ">=2.4.0", extras = ["crypto"]} +pynacl = ">=1.4.0" +requests = ">=2.14.0" + [[package]] name = "pygments" version = "2.15.1" @@ -2109,6 +3020,52 @@ files = [ [package.extras] plugins = ["importlib-metadata"] +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + [[package]] name = "pyproject-hooks" version = "1.0.0" @@ -2250,6 +3207,74 @@ psutil = ["psutil (>=3.0)"] setproctitle = ["setproctitle"] testing = ["filelock"] +[[package]] +name = "python-baseconv" +version = "1.2.2" +description = "Convert numbers from base 10 integers to base X strings and back again." +optional = false +python-versions = "*" +files = [ + {file = "python-baseconv-1.2.2.tar.gz", hash = "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "pyunormalize" +version = "15.0.0" +description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyunormalize-15.0.0.tar.gz", hash = "sha256:e63fdba0d85ea04579dde2fc29a072dba773dcae600b04faf6cc90714c8b1302"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.1" @@ -2548,21 +3573,21 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" -version = "13.4.2" +version = "12.6.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.6.3,<4.0.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, + {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, ] [package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" [package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "rlp" @@ -2674,6 +3699,84 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.20" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"}, + {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"}, + {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + [[package]] name = "stack-data" version = "0.6.2" @@ -2756,6 +3859,26 @@ files = [ {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, ] +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.9.0" @@ -2806,6 +3929,17 @@ files = [ {file = "trove_classifiers-2023.5.24-py3-none-any.whl", hash = "sha256:d9d7ae14fb90bf3d50bef99c3941b176b5326509e6e9037e622562d6352629d0"}, ] +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + [[package]] name = "urllib3" version = "1.26.16" @@ -2822,6 +3956,16 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "varint" +version = "1.0.2" +description = "Simple python varint implementation" +optional = false +python-versions = "*" +files = [ + {file = "varint-1.0.2.tar.gz", hash = "sha256:a6ecc02377ac5ee9d65a6a8ad45c9ff1dac8ccee19400a5950fb51d594214ca5"}, +] + [[package]] name = "virtualenv" version = "20.23.1" @@ -2866,6 +4010,45 @@ docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.910)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, + {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, + {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, + {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, + {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, + {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, + {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, + {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, + {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + [[package]] name = "wcwidth" version = "0.2.6" @@ -2877,6 +4060,43 @@ files = [ {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ] +[[package]] +name = "web3" +version = "6.9.0" +description = "web3.py" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "web3-6.9.0-py3-none-any.whl", hash = "sha256:3bc95043ee9fc6ee0b13a4766d4975b9f7cae069db136430a3799ed18743e608"}, + {file = "web3-6.9.0.tar.gz", hash = "sha256:cb454d0180e63ba1d83143dccf7c623581ba58e222edb006f48252d8a7b948e0"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4.post0" +eth-abi = ">=4.0.0" +eth-account = ">=0.8.0" +eth-hash = {version = ">=0.5.1", extras = ["pycryptodome"]} +eth-tester = {version = "v0.9.1-b.1", extras = ["py-evm"], optional = true, markers = "extra == \"tester\""} +eth-typing = ">=3.0.0" +eth-utils = ">=2.1.0" +hexbytes = ">=0.1.0" +jsonschema = ">=4.0.0" +lru-dict = ">=1.1.6" +protobuf = ">=4.21.6" +py-geth = {version = ">=3.11.0", optional = true, markers = "extra == \"tester\""} +pyunormalize = ">=15.0.0" +pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} +requests = ">=2.16.0" +typing-extensions = ">=4.0.1" +websockets = ">=10.0.0" + +[package.extras] +dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +ipfs = ["ipfshttpclient (==0.8.0a2)"] +linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] +tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] + [[package]] name = "webencodings" version = "0.5.1" @@ -2888,6 +4108,85 @@ files = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + [[package]] name = "wheel" version = "0.40.0" @@ -2912,6 +4211,90 @@ files = [ {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, ] +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + [[package]] name = "xattr" version = "0.10.1" @@ -2996,6 +4379,93 @@ files = [ [package.dependencies] cffi = ">=1.0" +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [[package]] name = "zipp" version = "3.15.0" @@ -3014,4 +4484,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9c10197ea9e8028bd48145b237b3af2216b57495ef714f0fc27dd602295679ca" +content-hash = "a217037d6e131fdf20301059bfe64d527a22557e8d3101afb00c106ccd8922fb" diff --git a/pyproject.toml b/pyproject.toml index 143cde9a..38f3fadf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "e29a7064 vyper = "^0.3.9" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" +eth-ape = "^0.6.18" [tool.poetry.group.dev.dependencies] black = "22.3.0" diff --git a/scripts/deploy.py b/scripts/deploy.py new file mode 100644 index 00000000..921d4708 --- /dev/null +++ b/scripts/deploy.py @@ -0,0 +1,65 @@ +import click +from ape import accounts +from ape.cli import NetworkBoundCommand, account_option, network_option +from eth_utils import to_checksum_address + +import scripts.deployment_utils as deploy_utils + + +def _get_deployed_contracts(): + pass + + +def _save_deployed_contracts(): + pass + + +def _deploy_pool_from_factory(): + pass + + +@click.group(short_help="Deploy the project") +def cli(): + pass + + +@cli.command(cls=NetworkBoundCommand) +@network_option() +@account_option() +def deploy_and_test_infra(network, account): + + if account.alias == "fiddydeployer": + account.set_autosign(True) + + if "ethereum:mainnet-fork" in network: + account = accounts["0xbabe61887f1de2713c6f97e567623453d3c79f67"] + + for _network, data in deploy_utils.curve_dao_network_settings.items(): + + if _network in network: + + owner = data.dao_ownership_contract + fee_receiver = data.fee_receiver_address + weth = to_checksum_address(data.weth_address) + + assert owner, f"Curve's DAO contracts may not be on {network}." + assert fee_receiver, f"Curve's DAO contracts may not be on {network}." + + # --------------------- DEPLOY FACTORY AND POOL --------------------------- + + factory = deploy_utils.deploy_amm_factory(account, fee_receiver, weth, network) + + pool = _deploy_pool_from_factory(network, account, factory, weth) + coins = [ + to_checksum_address(pool.coins(0)), + to_checksum_address(pool.coins(1)), + to_checksum_address(pool.coins(2)), + ] + + _account = account + if "ethereum:mainnet-fork" in network: # bridge + _account = accounts["0x8EB8a3b98659Cce290402893d0123abb75E3ab28"] + + deploy_utils.test_deployment(pool, coins, fee_receiver, _account) + + print("Success!") diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py new file mode 100644 index 00000000..3ee7a10c --- /dev/null +++ b/scripts/deployment_utils.py @@ -0,0 +1,154 @@ +from dataclasses import dataclass + +import click +from ape import networks, project +from ape.api.address import Address + +DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 + + +def _get_tx_params(): + + if "mainnet-fork" == networks.active_provider.network.name: + return {} + + if "sepolia" == networks.active_provider.network.name: + return {} + + active_provider = networks.active_provider + max_fee = int(active_provider.base_fee * 1.2) + max_priority_fee = int(0.5e9) + + return {"max_fee": max_fee, "max_priority_fee": max_priority_fee} + + +def deploy_blueprint(contract, account): + + initcode = contract.contract_type.deployment_bytecode.bytecode + if isinstance(initcode, str): + initcode = bytes.fromhex(initcode.removeprefix("0x")) + initcode = b"\xfe\x71\x00" + initcode # eip-5202 preamble version 0 + initcode = b"\x61" + len(initcode).to_bytes(2, "big") + b"\x3d\x81\x60\x0a\x3d\x39\xf3" + initcode + + tx = project.provider.network.ecosystem.create_transaction( + chain_id=project.provider.chain_id, + data=initcode, + gas_price=project.provider.gas_price, + nonce=account.nonce, + **_get_tx_params(), + ) + receipt = account.call(tx) + click.echo(f"blueprint deployed at: {receipt.contract_address}") + return receipt.contract_address + + +# -------------- CURVE DATA -------------- + + +@dataclass +class CurveNetworkSettings: + dao_ownership_contract: Address + fee_receiver_address: Address + usdc_address: Address + wbtc_address: Address + weth_address: Address + + +GAUGE_CONTROLLER = "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB" +ADDRESS_PROVIDER = "0x0000000022d53366457f9d5e68ec105046fc4383" + + +curve_dao_network_settings = { + "ethereum:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", + fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", + usdc_address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + wbtc_address="0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + weth_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + ), + "ethereum:sepolia": CurveNetworkSettings( + dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", + fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", + usdc_address="0x51fCe89b9f6D4c530698f181167043e1bB4abf89", + wbtc_address="0xFF82bB6DB46Ad45F017e2Dfb478102C7671B13b3", + weth_address="0xf531B8F309Be94191af87605CfBf600D71C2cFe0", + ), + "arbitrum:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", + fee_receiver_address="0xd4f94d0aaa640bbb72b5eec2d85f6d114d81a88e", + usdc_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # native usdc token. # noqa: E501 + wbtc_address="0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f", + weth_address="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + ), + "optimism:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", + fee_receiver_address="0xbF7E49483881C76487b0989CD7d9A8239B20CA41", + usdc_address="0x7f5c764cbc14f9669b88837ca1490cca17c31607", + wbtc_address="0x68f180fcce6836688e9084f035309e29bf0a2095", + weth_address="0x4200000000000000000000000000000000000006", + ), + "polygon:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", + fee_receiver_address="0x774D1Dba98cfBD1F2Bc3A1F59c494125e07C48F9", + usdc_address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + wbtc_address="0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + weth_address="0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + ), + "avalanche:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xbabe61887f1de2713c6f97e567623453d3c79f67", + fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", + usdc_address="0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", + wbtc_address="0x50b7545627a5162F82A992c33b87aDc75187B218", + weth_address="0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB", + ), + "gnosis:mainnet": CurveNetworkSettings( + dao_ownership_contract="", # <--- need to deploy sidechain ownership contract # noqa: E501 + fee_receiver_address="", # <--- need to deploy sidechain pool proxy + usdc_address="0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", + wbtc_address="0x8e5bBbb09Ed1ebdE8674Cda39A0c169401db4252", + weth_address="0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", + ), + "fantom:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- thin proxy # noqa: E501 + fee_receiver_address="0x2B039565B2b7a1A9192D4847fbd33B25b836B950", + usdc_address="0x04068DA6C83AFCFA0e13ba15A6696662335D5B75", # <-- multichain usdc # noqa: E501 + wbtc_address="0x321162Cd933E2Be498Cd2267a90534A804051b11", # <-- multichain wbtc # noqa: E501 + weth_address="0x74b23882a30290451A17c44f4F05243b6b58C76d", # <-- multichain weth # noqa: E501 + ), + "celo:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", # <-- needs to accept transfer ownership for 0x5277A0226d10392295E8D383E9724D6E416d6e6C # noqa: E501 + fee_receiver_address="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", # <-- Thin proxy, needs to be changed! # noqa: E501 + usdc_address="0x37f750b7cc259a2f741af45294f6a16572cf5cad", # <-- wormhole usdc # noqa: E501 + wbtc_address="", + weth_address="0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207", # <-- wormhole weth # noqa: E501 + ), + "kava:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + usdc_address="", + wbtc_address="", + weth_address="", + ), + "moonbeam": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + usdc_address="", + wbtc_address="", + weth_address="", + ), + "aurora": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + usdc_address="0xb12bfca5a55806aaf64e99521918a4bf0fc40802", + wbtc_address="", + weth_address="", + ), +} + + +CURVE_DAO_OWNERSHIP = { + "agent": "0x40907540d8a6c65c637785e8f8b742ae6b0b9968", + "voting": "0xe478de485ad2fe566d49342cbd03e49ed7db3356", + "token": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2", + "quorum": 30, +} diff --git a/scripts/simulate.py b/scripts/simulate.py new file mode 100644 index 00000000..e47bb5a0 --- /dev/null +++ b/scripts/simulate.py @@ -0,0 +1,37 @@ +import pprint + +import ape +from ape.logging import logger + +CONVEX_VOTERPROXY = "0x989AEB4D175E16225E39E87D0D97A3360524AD80" + + +def simulate(vote_id: int, voting_contract: str): + """Simulate passing vote on mainnet-fork""" + logger.info("--------- SIMULATE VOTE ---------") + + aragon = ape.Contract(voting_contract) + + # print vote details to console first: + logger.info("Vote stats before Convex Vote:") + vote_stats = aragon.getVote(vote_id) + logger.info(pprint.pformat(vote_stats, indent=4)) + + # vote + logger.info("Simulate Convex 'yes' vote") + aragon.vote(vote_id, True, False, sender=ape.accounts[CONVEX_VOTERPROXY]) + + # sleep for a week so it has time to pass + num_blocks = int(aragon.voteTime() + 200 / 10) + ape.chain.mine(num_blocks) + + # get vote stats: + logger.info("Vote stats after 1 week:") + vote_stats = aragon.getVote(vote_id) + logger.debug(pprint.pformat(vote_stats, indent=4)) + + # moment of truth - execute the vote! + logger.info("Simulate proposal execution") + enacter = ape.accounts[CONVEX_VOTERPROXY] + aragon.executeVote(vote_id, sender=enacter) + logger.info("Vote Executed!") diff --git a/scripts/vote_utils.py b/scripts/vote_utils.py new file mode 100644 index 00000000..fd7defe7 --- /dev/null +++ b/scripts/vote_utils.py @@ -0,0 +1,133 @@ +import json +import os +import pprint +import warnings +from typing import Dict, List, Tuple + +import ape +import requests +from ape.logging import logger + +warnings.filterwarnings("ignore") + +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" +CONVEX_VOTERPROXY = "0x989AEB4D175E16225E39E87D0D97A3360524AD80" +CURVE_DAO_OWNERSHIP = { + "agent": "0x40907540d8a6c65c637785e8f8b742ae6b0b9968", + "voting": "0xe478de485ad2fe566d49342cbd03e49ed7db3356", + "token": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2", + "quorum": 30, +} +BOSS = { + "ethereum": "0xEdf2C58E16Cc606Da1977e79E1e69e79C54fe242", + "arbitrum": "0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", + "optimism": "0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", + "polygon": "0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", + "gnosis": "0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", +} +FIDDY = "0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17" + + +def prepare_evm_script(target: Dict, actions: List[Tuple]) -> str: + """Generates EVM script to be executed by AragonDAO contracts. + + Args: + target (dict): CURVE_DAO_OWNERSHIP / CURVE_DAO_PARAMS / EMERGENCY_DAO + actions (list(tuple)): ("target addr", "fn_name", *args) + + Returns: + str: Generated EVM script. + """ + agent = ape.Contract(target["agent"]) + voting = target["voting"] + + logger.info(f"Agent Contract: {agent.address}") + logger.info(f"Voting Contract: {voting}") + + evm_script = "0x00000001" + + for address, fn_name, *args in actions: + + contract = ape.Contract(address) + fn = getattr(contract, fn_name) + calldata = fn.as_transaction(*args, sender=agent.address, gas_price=0).data + agent_calldata = agent.execute.as_transaction(address, 0, calldata, sender=voting, gas_price=0).data + length = hex(len(agent_calldata.hex()) // 2)[2:].zfill(8) + evm_script = f"{evm_script}{agent.address[2:]}{length}{agent_calldata.hex()}" + + return evm_script + + +def get_vote_description_ipfs_hash(description: str): + """Uploads vote description to IPFS and returns the IPFS hash. + + NOTE: needs environment variables for infura IPFS access. Please + set up an IPFS project to generate project id and project secret! + """ + text = json.dumps({"text": description}) + response = requests.post( + "https://ipfs.infura.io:5001/api/v0/add", + files={"file": text}, + auth=(os.getenv("IPFS_PROJECT_ID"), os.getenv("IPFS_PROJECT_SECRET")), + ) + return response.json()["Hash"] + + +def make_vote(target: Dict, actions: List[Tuple], description: str, vote_creator: str): + """Prepares EVM script and creates an on-chain AragonDAO vote. + + Args: + target (dict): ownership / parameter / emergency + actions (list(tuple)): ("target addr", "fn_name", *args) + vote_creator (str): msg.sender address + description (str): Description of the on-chain governance proposal + + Returns: + str: vote ID of the created vote. + """ + aragon = ape.Contract(target["voting"]) + assert aragon.canCreateNewVote(vote_creator), "dev: user cannot create new vote" + + evm_script = prepare_evm_script(target, actions) + logger.debug(f"EVM script: {evm_script}") + + tx = aragon.newVote( + evm_script, + f"ipfs:{get_vote_description_ipfs_hash(description)}", + False, + False, + gas_limit=500000, + sender=vote_creator, + ) + return tx.events[0].voteId + + +def simulate(vote_id: int, voting_contract: str): + """Simulate passing vote on mainnet-fork""" + logger.info("--------- SIMULATE VOTE ---------") + + aragon = ape.Contract(voting_contract) + + # print vote details to console first: + logger.info("Vote stats before Convex Vote:") + vote_stats = aragon.getVote(vote_id) + logger.info(pprint.pformat(vote_stats, indent=4)) + + # vote + logger.info("Simulate Convex 'yes' vote") + aragon.vote(vote_id, True, False, sender=ape.accounts[CONVEX_VOTERPROXY]) + + # sleep for a week so it has time to pass + num_blocks = int(aragon.voteTime() + 200 / 10) + ape.chain.mine(num_blocks) + + # get vote stats: + logger.info("Vote stats after 1 week:") + vote_stats = aragon.getVote(vote_id) + logger.debug(pprint.pformat(vote_stats, indent=4)) + + # moment of truth - execute the vote! + logger.info("Simulate proposal execution") + enacter = ape.accounts[CONVEX_VOTERPROXY] + aragon.executeVote(vote_id, sender=enacter) + logger.info("Vote Executed!") From 031b068e088601413a1ced30162dfaaf5781bab5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:44:17 +0200 Subject: [PATCH 152/337] init --- scripts/deploy.py | 82 +++++++++++----- scripts/deployment_utils.py | 190 +++++++++++++++++++++++++++--------- 2 files changed, 206 insertions(+), 66 deletions(-) diff --git a/scripts/deploy.py b/scripts/deploy.py index 921d4708..3759f709 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -1,12 +1,12 @@ import click -from ape import accounts +from ape import Contract, accounts, project from ape.cli import NetworkBoundCommand, account_option, network_option -from eth_utils import to_checksum_address +from ape.logging import logger import scripts.deployment_utils as deploy_utils -def _get_deployed_contracts(): +def _get_deployed_contracts(): # noqa: F pass @@ -14,10 +14,6 @@ def _save_deployed_contracts(): pass -def _deploy_pool_from_factory(): - pass - - @click.group(short_help="Deploy the project") def cli(): pass @@ -39,27 +35,69 @@ def deploy_and_test_infra(network, account): if _network in network: owner = data.dao_ownership_contract - fee_receiver = data.fee_receiver_address - weth = to_checksum_address(data.weth_address) + fee_receiver = data.fee_receiver_address2 assert owner, f"Curve's DAO contracts may not be on {network}." assert fee_receiver, f"Curve's DAO contracts may not be on {network}." - # --------------------- DEPLOY FACTORY AND POOL --------------------------- + with accounts.use_sender(account): + + # --------------------- Deploy math, views, blueprints --------------------- + + math_contract = project.CurveStableSwapNGMath.deploy() + views_contract = project.CurveStableSwapNGViews.deploy() + plain_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapNG, account) + meta_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapMetaNG, account) + gauge_blueprint_contract = deploy_utils.deploy_blueprint(project.LiquidityGauge, account) + + # --------------------- DEPLOY FACTORY --------------------------- + + factory = project.CurveStableSwapFactoryNG.deploy( + deploy_utils.curve_dao_network_settings[network], # fee_receiver + deploy_utils.FIDDYRESEARCH, # owner (temporary) + ) + + factory.set_gauge_implementation(gauge_blueprint_contract) + factory.set_views_implementation(views_contract) + factory.set_math_implementation(math_contract) + + factory.set_pool_implementations(0, plain_blueprint_contract) + factory.set_metapool_implementations(0, meta_blueprint_contract) + + # -------------------------- Add base pools -------------------------- + + base_pool_data = deploy_utils.base_pool_list[network] + if base_pool_data: # check if network has base pools: + for data in base_pool_data: + factory.add_base_pool( + data.pool, + data.lp_token, + data.coins, + data.asset_types, + data.n_coins, + ) + + # -------------------------- Set up metaregistry -------------------------- - factory = deploy_utils.deploy_amm_factory(account, fee_receiver, weth, network) + metaregistry_address = deploy_utils.curve_dao_network_settings[network].metaregistry_address - pool = _deploy_pool_from_factory(network, account, factory, weth) - coins = [ - to_checksum_address(pool.coins(0)), - to_checksum_address(pool.coins(1)), - to_checksum_address(pool.coins(2)), - ] + if metaregistry_address: - _account = account - if "ethereum:mainnet-fork" in network: # bridge - _account = accounts["0x8EB8a3b98659Cce290402893d0123abb75E3ab28"] + metaregistry = Contract(metaregistry_address) + boss = Contract(metaregistry.owner()) - deploy_utils.test_deployment(pool, coins, fee_receiver, _account) + # set up metaregistry integration: + logger.info("Integrate into Metaregistry ...") + logger.info("Deploying Factory handler to integrate it to the metaregistry:") + factory_handler = account.deploy( + project.CurveStableSwapFactoryNGHandler, + factory.address, + deploy_utils.curve_dao_network_settings[network].base_pool_address, + **deploy_utils._get_tx_params(), + ) - print("Success!") + boss.execute( + metaregistry.address, + metaregistry.add_registry_handler.encode_input(factory_handler), + **deploy_utils._get_tx_params(), + ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 3ee7a10c..79947716 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import List import click from ape import networks, project @@ -42,6 +43,14 @@ def deploy_blueprint(contract, account): return receipt.contract_address +# ------ IMMUTABLES ------ + + +GAUGE_CONTROLLER = "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB" +ADDRESS_PROVIDER = "0x0000000022d53366457f9d5e68ec105046fc4383" +FIDDYRESEARCH = "0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17" + + # -------------- CURVE DATA -------------- @@ -49,99 +58,60 @@ def deploy_blueprint(contract, account): class CurveNetworkSettings: dao_ownership_contract: Address fee_receiver_address: Address - usdc_address: Address - wbtc_address: Address - weth_address: Address - - -GAUGE_CONTROLLER = "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB" -ADDRESS_PROVIDER = "0x0000000022d53366457f9d5e68ec105046fc4383" + metaregistry_address: Address = "" + base_pool_address: Address = "" curve_dao_network_settings = { "ethereum:mainnet": CurveNetworkSettings( dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", - usdc_address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - wbtc_address="0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - weth_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", + base_pool_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", ), "ethereum:sepolia": CurveNetworkSettings( dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", - usdc_address="0x51fCe89b9f6D4c530698f181167043e1bB4abf89", - wbtc_address="0xFF82bB6DB46Ad45F017e2Dfb478102C7671B13b3", - weth_address="0xf531B8F309Be94191af87605CfBf600D71C2cFe0", ), "arbitrum:mainnet": CurveNetworkSettings( dao_ownership_contract="0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", fee_receiver_address="0xd4f94d0aaa640bbb72b5eec2d85f6d114d81a88e", - usdc_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # native usdc token. # noqa: E501 - wbtc_address="0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f", - weth_address="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", ), "optimism:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", fee_receiver_address="0xbF7E49483881C76487b0989CD7d9A8239B20CA41", - usdc_address="0x7f5c764cbc14f9669b88837ca1490cca17c31607", - wbtc_address="0x68f180fcce6836688e9084f035309e29bf0a2095", - weth_address="0x4200000000000000000000000000000000000006", ), "polygon:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", fee_receiver_address="0x774D1Dba98cfBD1F2Bc3A1F59c494125e07C48F9", - usdc_address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", - wbtc_address="0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", - weth_address="0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", ), "avalanche:mainnet": CurveNetworkSettings( dao_ownership_contract="0xbabe61887f1de2713c6f97e567623453d3c79f67", fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", - usdc_address="0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", - wbtc_address="0x50b7545627a5162F82A992c33b87aDc75187B218", - weth_address="0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB", ), "gnosis:mainnet": CurveNetworkSettings( dao_ownership_contract="", # <--- need to deploy sidechain ownership contract # noqa: E501 fee_receiver_address="", # <--- need to deploy sidechain pool proxy - usdc_address="0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", - wbtc_address="0x8e5bBbb09Ed1ebdE8674Cda39A0c169401db4252", - weth_address="0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", ), "fantom:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- thin proxy # noqa: E501 fee_receiver_address="0x2B039565B2b7a1A9192D4847fbd33B25b836B950", - usdc_address="0x04068DA6C83AFCFA0e13ba15A6696662335D5B75", # <-- multichain usdc # noqa: E501 - wbtc_address="0x321162Cd933E2Be498Cd2267a90534A804051b11", # <-- multichain wbtc # noqa: E501 - weth_address="0x74b23882a30290451A17c44f4F05243b6b58C76d", # <-- multichain weth # noqa: E501 ), "celo:mainnet": CurveNetworkSettings( dao_ownership_contract="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", # <-- needs to accept transfer ownership for 0x5277A0226d10392295E8D383E9724D6E416d6e6C # noqa: E501 - fee_receiver_address="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", # <-- Thin proxy, needs to be changed! # noqa: E501 - usdc_address="0x37f750b7cc259a2f741af45294f6a16572cf5cad", # <-- wormhole usdc # noqa: E501 - wbtc_address="", - weth_address="0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207", # <-- wormhole weth # noqa: E501 + fee_receiver_address="", ), "kava:mainnet": CurveNetworkSettings( dao_ownership_contract="", fee_receiver_address="", - usdc_address="", - wbtc_address="", - weth_address="", ), "moonbeam": CurveNetworkSettings( dao_ownership_contract="", fee_receiver_address="", - usdc_address="", - wbtc_address="", - weth_address="", ), "aurora": CurveNetworkSettings( dao_ownership_contract="", fee_receiver_address="", - usdc_address="0xb12bfca5a55806aaf64e99521918a4bf0fc40802", - wbtc_address="", - weth_address="", ), } @@ -152,3 +122,135 @@ class CurveNetworkSettings: "token": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2", "quorum": 30, } + +# ------------- BASE POOLS --------------- + + +@dataclass +class BasePoolSettings: + pool: Address + lp_token: Address + coins: List[Address] + asset_types: List[int] + n_coins: int + + +base_pool_list = { + "ethereum:mainnet": [ + BasePoolSettings( # 3pool + pool="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", + lp_token="0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490", + coins=[ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", # dai + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc + "0xdAC17F958D2ee523a2206206994597C13D831ec7", # usdt + ], + asset_types=[0, 0, 0], + n_coins=3, + ), + BasePoolSettings( # fraxusdc + pool="0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2", + lp_token="0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC", + coins=[ + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc + ], + asset_types=[0, 0], + n_coins=2, + ), + BasePoolSettings( # sbtc/wbtc + pool="0xf253f83AcA21aAbD2A20553AE0BF7F65C755A07F", + lp_token="0x051d7e5609917Bd9b73f04BAc0DED8Dd46a74301", + coins=[ + "0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6", # sbtc + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", # wbtc + ], + asset_types=[0, 0], + n_coins=2, + ), + BasePoolSettings( + pool="0xaE34574AC03A15cd58A92DC79De7B1A0800F1CE3", + lp_token="0xFC2838a17D8e8B1D5456E0a351B0708a09211147", + coins=[ + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0x8E870D67F660D95d5be530380D0eC0bd388289E1", # usdp + ], + asset_types=[0, 0], + n_coins=2, + ), + ], + "arbitrum:mainnet": [], + "optimism:mainnet": [], + "gnosis:mainnet": [], + "polygon:mainnet": [], + "base:mainnet": [], +} + + +# -------------------------- POOL SETUP -------------------------- + + +@dataclass +class PoolSettings: + name: str + symbol: str + coins: List[Address] + A: int + fee: int + offpeg_fee_multiplier: int + ma_exp_time: int + implementation_idx: int + asset_types: List[int] + method_ids: List[int] + oracles: List[Address] + + +plain = { + "ethereum:mainnet": PoolSettings(name=""), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} +oracle = { + "ethereum:mainnet": PoolSettings(), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} +rebasing = { + "ethereum:mainnet": PoolSettings(), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} + +meta_plain = { + "ethereum:mainnet": PoolSettings(), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} +meta_oracle = { + "ethereum:mainnet": PoolSettings(), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} +meta_rebasing = { + "ethereum:mainnet": PoolSettings(), + "arbitrum:mainnet": PoolSettings(), + "optimism:mainnet": PoolSettings(), + "gnosis:mainnet": PoolSettings(), + "polygon:mainnet": PoolSettings(), + "base:mainnet": PoolSettings(), +} From ad1eac932fd0a175bc9b01e626230305167c5f46 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:36:43 +0200 Subject: [PATCH 153/337] add addressprovider and metaregistry integration code --- scripts/deploy.py | 107 ++++++++++++++++++++++++++++++------ scripts/deployment_utils.py | 3 +- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/scripts/deploy.py b/scripts/deploy.py index 3759f709..65574393 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -6,14 +6,6 @@ import scripts.deployment_utils as deploy_utils -def _get_deployed_contracts(): # noqa: F - pass - - -def _save_deployed_contracts(): - pass - - @click.group(short_help="Deploy the project") def cli(): pass @@ -22,14 +14,11 @@ def cli(): @cli.command(cls=NetworkBoundCommand) @network_option() @account_option() -def deploy_and_test_infra(network, account): +def deploy_infra(network, account): if account.alias == "fiddydeployer": account.set_autosign(True) - if "ethereum:mainnet-fork" in network: - account = accounts["0xbabe61887f1de2713c6f97e567623453d3c79f67"] - for _network, data in deploy_utils.curve_dao_network_settings.items(): if _network in network: @@ -44,6 +33,7 @@ def deploy_and_test_infra(network, account): # --------------------- Deploy math, views, blueprints --------------------- + logger.info("Deploying AMM components ...") math_contract = project.CurveStableSwapNGMath.deploy() views_contract = project.CurveStableSwapNGViews.deploy() plain_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapNG, account) @@ -52,20 +42,24 @@ def deploy_and_test_infra(network, account): # --------------------- DEPLOY FACTORY --------------------------- + logger.info("Deploying factory ...") factory = project.CurveStableSwapFactoryNG.deploy( deploy_utils.curve_dao_network_settings[network], # fee_receiver deploy_utils.FIDDYRESEARCH, # owner (temporary) ) - factory.set_gauge_implementation(gauge_blueprint_contract) - factory.set_views_implementation(views_contract) - factory.set_math_implementation(math_contract) + logger.info("Integrating AMM components into factory ...") + + factory.set_gauge_implementation(gauge_blueprint_contract, **deploy_utils._get_tx_params()) + factory.set_views_implementation(views_contract, **deploy_utils._get_tx_params()) + factory.set_math_implementation(math_contract, **deploy_utils._get_tx_params()) - factory.set_pool_implementations(0, plain_blueprint_contract) - factory.set_metapool_implementations(0, meta_blueprint_contract) + factory.set_pool_implementations(0, plain_blueprint_contract, **deploy_utils._get_tx_params()) + factory.set_metapool_implementations(0, meta_blueprint_contract, **deploy_utils._get_tx_params()) # -------------------------- Add base pools -------------------------- + logger.info("Setting up base pools ...") base_pool_data = deploy_utils.base_pool_list[network] if base_pool_data: # check if network has base pools: for data in base_pool_data: @@ -75,8 +69,83 @@ def deploy_and_test_infra(network, account): data.coins, data.asset_types, data.n_coins, + **deploy_utils._get_tx_params(), + ) + + +@cli.command(cls=NetworkBoundCommand) +@network_option() +@account_option() +@click.option("--factory", required=True, type=str) +def set_up_registries(network, account, factory): + + if account.alias == "fiddydeployer": + account.set_autosign(True) + + for _network, data in deploy_utils.curve_dao_network_settings.items(): + + if _network in network: + + owner = data.dao_ownership_contract + fee_receiver = data.fee_receiver_address + address_provider = Contract(data.address_provider) + + assert owner, f"Curve's DAO contracts may not be on {network}." + assert fee_receiver, f"Curve's DAO contracts may not be on {network}." + + with accounts.use_sender(account): + + # -------------------------- Register into AddressProvider -------------------------- + + max_id = address_provider.max_id() + description = "Curve StableSwapNG" + boss = Contract(address_provider.admin()) + + # check if account can handle boss: + account_is_boss_handler = False + for i in range(2): + if account.address.lower() == boss.admins(i).lower(): + account_is_boss_handler = True + break + + assert account_is_boss_handler # only authorised accounts can write to address provider # noqa: E501 + + for index in range(max_id + 1): + if address_provider.get_id_info(index).description is description: + break + + if index == max_id: + + logger.info(f"Adding a new registry provider entry at id: {index + 1}") + + # we're adding a new id + with accounts.use_sender(account) as account: + boss.execute( + address_provider.address, + address_provider.add_new_id.encode_input(factory, description), + gas_limit=400000, + **deploy_utils._get_tx_params(), ) + else: + + assert address_provider.get_id_info(index).description == description + + logger.info(f"Updating existing registry provider entry at id: {index}") + + # we're updating an existing id with the same description: + with accounts.use_sender(account) as account: + boss.execute( + address_provider.address, + address_provider.set_address.encode_input(index, factory), + gas_limit=200000, + **deploy_utils._get_tx_params(), + ) + + assert address_provider.get_id_info(index).addr.lower() == factory.lower() + + logger.info("AddressProvider integration complete!") + # -------------------------- Set up metaregistry -------------------------- metaregistry_address = deploy_utils.curve_dao_network_settings[network].metaregistry_address @@ -92,7 +161,7 @@ def deploy_and_test_infra(network, account): factory_handler = account.deploy( project.CurveStableSwapFactoryNGHandler, factory.address, - deploy_utils.curve_dao_network_settings[network].base_pool_address, + deploy_utils.curve_dao_network_settings[network].base_pool_registry_address, **deploy_utils._get_tx_params(), ) @@ -101,3 +170,5 @@ def deploy_and_test_infra(network, account): metaregistry.add_registry_handler.encode_input(factory_handler), **deploy_utils._get_tx_params(), ) + + logger.info("Metaregistry integration complete!") diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 79947716..24a661db 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -60,6 +60,7 @@ class CurveNetworkSettings: fee_receiver_address: Address metaregistry_address: Address = "" base_pool_address: Address = "" + address_provider: Address = "0x0000000022d53366457f9d5e68ec105046fc4383" curve_dao_network_settings = { @@ -67,7 +68,7 @@ class CurveNetworkSettings: dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", - base_pool_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", + base_pool_registry_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", ), "ethereum:sepolia": CurveNetworkSettings( dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", From a34ecc3323cb74ea97784ec20da8969980657a16 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:27:52 +0200 Subject: [PATCH 154/337] fix: incorrect D_P calculations in get_D; deployment scripts; some fixes to comments; fixes to ape config; --- ape-config.yaml | 4 +- contracts/main/CurveStableSwapFactoryNG.vy | 9 +- contracts/main/CurveStableSwapMetaNG.vy | 30 +++-- contracts/main/CurveStableSwapNG.vy | 38 +++--- contracts/main/CurveStableSwapNGMath.vy | 6 +- scripts/deploy.py | 22 ++-- scripts/deployment_utils.py | 87 ++++++------- tests/pools/test_exchange_3coins.py | 134 +++++++++++++++++++++ 8 files changed, 225 insertions(+), 105 deletions(-) create mode 100644 tests/pools/test_exchange_3coins.py diff --git a/ape-config.yaml b/ape-config.yaml index adb1d441..5422b784 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -15,7 +15,9 @@ plugins: default_ecosystem: ethereum # vyper: -# evm_version: paris # enable for non PUSH0 evm networks +# default_version: paris # enable for non PUSH0 evm networks +# ethereum: +# evm_version: shanghai hardhat: port: auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index a30a6bf2..eb722042 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -600,9 +600,6 @@ def deploy_metapool( @param _fee Trade fee, given as an integer with 1e10 precision. The the maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. - @param _implementation_idx Index of the implementation to use. All possible - implementations for a BASE_POOL can be publicly accessed - via `metapool_implementations(BASE_POOL)` @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @@ -794,7 +791,7 @@ def set_math_implementation(_math_implementation: address): """ @notice Set implementation contracts for StableSwap Math @dev Only callable by admin - @param _implementation Implementation address to use when stableswap pools + @param _math_implementation Address of the math implementation contract """ assert msg.sender == self.admin # dev: admin-only function self.math_implementation = _math_implementation @@ -805,7 +802,7 @@ def set_gauge_implementation(_gauge_implementation: address): """ @notice Set implementation contracts for liquidity gauge @dev Only callable by admin - @param _implementation Implementation address to use when deploying gauges + @param _gauge_implementation Address of the gauge blueprint implementation contract """ assert msg.sender == self.admin # dev: admin-only function self.gauge_implementation = _gauge_implementation @@ -816,7 +813,7 @@ def set_views_implementation(_views_implementation: address): """ @notice Set implementation contracts for Views methods @dev Only callable by admin - @param _implementation Implementation address of views contract + @param _views_implementation Implementation address of views contract """ assert msg.sender == self.admin # dev: admin-only function self.views_implementation = _views_implementation diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 1b6bb47f..2308067d 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -9,8 +9,8 @@ exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the nth coin index of the base pool. Asset Types: - 0. Basic ERC20 token with no additional features - 1. Oracle - token with rate oracle + 0. Standard ERC20 token with no additional features + 1. Oracle - token with rate oracle (e.g. wstETH) 2. Rebasing - token with rebase (e.g. stETH) Supports: 1. ERC20 support for return True/revert, return True/False, return None @@ -286,8 +286,8 @@ def __init__( @param _base_pool The underlying AMM of the LP token _coins[0] is paired against @param _coins List of addresses of the coins being used in the pool. For metapool this is the coin (say LUSD) vs (say) 3crv as: [LUSD, 3CRV]. Length is always 2. - @params _base_coins coins in the underlying base pool. - @params _rate_multipliers Rate multipliers of the individual coins. For Metapools it is: + @param _base_coins coins in the underlying base pool. + @param _rate_multipliers Rate multipliers of the individual coins. For Metapools it is: [10 ** (36 - _coins[0].decimals()), 10 ** 18]. @param _asset_types Array of uint8 representing tokens in pool @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures @@ -371,12 +371,12 @@ def _transfer_in( ) -> uint256: """ @notice Contains all logic to handle ERC20 token transfers. - @params _coin address of the coin to transfer in. - @params dx amount of `_coin` to transfer into the pool. - @params dy amount of `_coin` to transfer out of the pool. - @params sender address to transfer `_coin` from. - @params receiver address to transfer `_coin` to. - @params expect_optimistic_transfer True if contract expects an optimistic coin transfer + @param _coin address of the coin to transfer in. + @param dx amount of `_coin` to transfer into the pool. + @param dy amount of `_coin` to transfer out of the pool. + @param sender address to transfer `_coin` from. + @param receiver address to transfer `_coin` to. + @param expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) _incoming_coin_asset_type: uint8 = asset_types[coin_metapool_idx] @@ -424,9 +424,9 @@ def _transfer_out( @notice Transfer a single token from the pool to receiver. @dev This function is called by `remove_liquidity` and `remove_liquidity_one` and `_exchange` methods. - @params _coin Address of the token to transfer out - @params _amount Amount of token to transfer out - @params receiver Address to send the tokens to + @param _coin Address of the token to transfer out + @param _amount Amount of token to transfer out + @param receiver Address to send the tokens to """ # ------------------------- Handle Transfers ----------------------------- @@ -543,7 +543,6 @@ def exchange_received( this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens directly to the contract and call `exchange_received`. - The method is non-payable: does not accept native token. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -1593,8 +1592,7 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: def totalSupply() -> uint256: """ @notice The total supply of pool LP tokens - @dev reentrancy guarded, just in case. - @returns self.total_supply, 18 decimals. + @return self.total_supply, 18 decimals. """ return self.total_supply diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index ac7b8958..99852843 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -9,8 +9,8 @@ The Pool contract also records exponential moving averages for coins 1, 2 and 3 relative to coin 0. @dev Asset Types: - 0. Basic ERC20 token with no additional features - 1. Oracle - token with rate oracle + 0. Standard ERC20 token with no additional features + 1. Oracle - token with rate oracle (e.g. wstETH) 2. Rebasing - token with rebase (e.g. stETH) Supports: 1. ERC20 support for return True/revert, return True/False, return None @@ -319,13 +319,12 @@ def _transfer_in( ) -> uint256: """ @notice Contains all logic to handle ERC20 token transfers. - @params _coin address of the coin to transfer in. - @params dx amount of `_coin` to transfer into the pool. - @params dy amount of `_coin` to transfer out of the pool. - @params mvalue msg.value if the transfer is ETH, 0 otherwise. - @params sender address to transfer `_coin` from. - @params receiver address to transfer `_coin` to. - @params expect_optimistic_transfer True if contract expects an optimistic coin transfer + @param _coin address of the coin to transfer in. + @param dx amount of `_coin` to transfer into the pool. + @param dy amount of `_coin` to transfer out of the pool. + @param sender address to transfer `_coin` from. + @param receiver address to transfer `_coin` to. + @param expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) _incoming_coin_asset_type: uint8 = asset_types[coin_idx] @@ -365,9 +364,9 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): @notice Transfer a single token from the pool to receiver. @dev This function is called by `remove_liquidity` and `remove_liquidity_one` and `_exchange` methods. - @params _coin Address of the token to transfer out - @params _amount Amount of token to transfer out - @params receiver Address to send the tokens to + @param _coin Address of the token to transfer out + @param _amount Amount of token to transfer out + @param receiver Address to send the tokens to """ # ------------------------- Handle Transfers ----------------------------- @@ -452,8 +451,6 @@ def exchange( """ @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method - Allows for native token swaps (e.g. ETH <> whatever) - If native token is not in coin list and msg.value > 0, swap will revert @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -487,7 +484,6 @@ def exchange_received( this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens directly to the contract and call `exchange_received`. - The method is non-payable: does not accept native token. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -1001,15 +997,15 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: Ann: uint256 = _amp * N_COINS D_P: uint256 = 0 Dprev: uint256 = 0 - N_pow_N: uint256 = pow_mod256(N_COINS, N_COINS) for i in range(255): - # D * D / _xp[0] * D / _xp[1] / N_COINS**N_COINS - D_P = unsafe_div(D * D / _xp[0] * D / _xp[1], N_pow_N) + D_P = D + for x in _xp: + D_P = D_P * D / (x * N_COINS) Dprev = D - # (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) + # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) D = ( (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * D / ( @@ -1017,6 +1013,7 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: unsafe_add(N_COINS, 1) * D_P ) ) + # Equality with the precision of 1 if D > Dprev: if D - Dprev <= 1: @@ -1611,8 +1608,7 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: def totalSupply() -> uint256: """ @notice The total supply of pool LP tokens - @dev reentrancy guarded, just in case. - @returns self.total_supply, 18 decimals. + @return self.total_supply, 18 decimals. """ return self.total_supply diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 22b364f5..09d1c6aa 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -109,12 +109,12 @@ def get_D( Ann: uint256 = _amp * _n_coins D_P: uint256 = 0 Dprev: uint256 = 0 - N_pow_N: uint256 = pow_mod256(_n_coins, _n_coins) for i in range(255): - # D * D / _xp[0] * D / _xp[1] / _n_coins**_n_coins - D_P = unsafe_div(D * D / _xp[0] * D / _xp[1], N_pow_N) + D_P = D + for x in _xp: + D_P = D_P * D / (x * _n_coins) # If division by 0, this will be borked: only withdrawal will work. And that is good Dprev = D # (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) diff --git a/scripts/deploy.py b/scripts/deploy.py index 65574393..bce71d92 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -24,7 +24,7 @@ def deploy_infra(network, account): if _network in network: owner = data.dao_ownership_contract - fee_receiver = data.fee_receiver_address2 + fee_receiver = data.fee_receiver_address assert owner, f"Curve's DAO contracts may not be on {network}." assert fee_receiver, f"Curve's DAO contracts may not be on {network}." @@ -34,8 +34,14 @@ def deploy_infra(network, account): # --------------------- Deploy math, views, blueprints --------------------- logger.info("Deploying AMM components ...") - math_contract = project.CurveStableSwapNGMath.deploy() - views_contract = project.CurveStableSwapNGViews.deploy() + math_contract = account.deploy( + project.CurveStableSwapNGMath, + **deploy_utils._get_tx_params(), + ) + views_contract = account.deploy( + project.CurveStableSwapNGViews, + **deploy_utils._get_tx_params(), + ) plain_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapNG, account) meta_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapMetaNG, account) gauge_blueprint_contract = deploy_utils.deploy_blueprint(project.LiquidityGauge, account) @@ -43,9 +49,10 @@ def deploy_infra(network, account): # --------------------- DEPLOY FACTORY --------------------------- logger.info("Deploying factory ...") - factory = project.CurveStableSwapFactoryNG.deploy( - deploy_utils.curve_dao_network_settings[network], # fee_receiver - deploy_utils.FIDDYRESEARCH, # owner (temporary) + factory = account.deploy( + project.CurveStableSwapFactoryNG, + deploy_utils.curve_dao_network_settings[network].fee_receiver_address, # fee_receiver + account, # owner (temporary) ) logger.info("Integrating AMM components into factory ...") @@ -149,6 +156,7 @@ def set_up_registries(network, account, factory): # -------------------------- Set up metaregistry -------------------------- metaregistry_address = deploy_utils.curve_dao_network_settings[network].metaregistry_address + base_pool_registry_address = deploy_utils.curve_dao_network_settings[network].base_pool_registry_address if metaregistry_address: @@ -161,7 +169,7 @@ def set_up_registries(network, account, factory): factory_handler = account.deploy( project.CurveStableSwapFactoryNGHandler, factory.address, - deploy_utils.curve_dao_network_settings[network].base_pool_registry_address, + base_pool_registry_address, **deploy_utils._get_tx_params(), ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 24a661db..67ae3f4e 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -6,6 +6,7 @@ from ape.api.address import Address DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" def _get_tx_params(): @@ -59,7 +60,7 @@ class CurveNetworkSettings: dao_ownership_contract: Address fee_receiver_address: Address metaregistry_address: Address = "" - base_pool_address: Address = "" + base_pool_registry_address: Address = "" address_provider: Address = "0x0000000022d53366457f9d5e68ec105046fc4383" @@ -70,6 +71,12 @@ class CurveNetworkSettings: metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", base_pool_registry_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", ), + "ethereum:mainnet-fork": CurveNetworkSettings( + dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", + fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", + metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", + base_pool_registry_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", + ), "ethereum:sepolia": CurveNetworkSettings( dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", @@ -136,7 +143,7 @@ class BasePoolSettings: n_coins: int -base_pool_list = { +_base_pool_list = { "ethereum:mainnet": [ BasePoolSettings( # 3pool pool="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", @@ -186,7 +193,8 @@ class BasePoolSettings: "polygon:mainnet": [], "base:mainnet": [], } - +_base_pool_list["ethereum:mainnet-fork"] = _base_pool_list["ethereum:mainnet"] +base_pool_list = _base_pool_list # -------------------------- POOL SETUP -------------------------- @@ -206,52 +214,29 @@ class PoolSettings: oracles: List[Address] -plain = { - "ethereum:mainnet": PoolSettings(name=""), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), -} -oracle = { - "ethereum:mainnet": PoolSettings(), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), -} -rebasing = { - "ethereum:mainnet": PoolSettings(), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), -} - -meta_plain = { - "ethereum:mainnet": PoolSettings(), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), -} -meta_oracle = { - "ethereum:mainnet": PoolSettings(), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), -} -meta_rebasing = { - "ethereum:mainnet": PoolSettings(), - "arbitrum:mainnet": PoolSettings(), - "optimism:mainnet": PoolSettings(), - "gnosis:mainnet": PoolSettings(), - "polygon:mainnet": PoolSettings(), - "base:mainnet": PoolSettings(), +pool_settings = { + "gnosis:mainnet": { + "plain": [ + "WXDAI<>USDC<>USDT", # name + "3pool-ng", # symbol + [ + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", # wxdai + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", # usdc + "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", # usdt + ], + 1000, # A + 4000000, # fee + 20000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + [0, 0, 0], # asset_types + [0, 0, 0], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles + ], + "oracle": [], + "rebasing": [], + "meta-plain": [], + "meta-oracle": [], + "meta-rebasing": [], + } } diff --git a/tests/pools/test_exchange_3coins.py b/tests/pools/test_exchange_3coins.py new file mode 100644 index 00000000..0c42bb2f --- /dev/null +++ b/tests/pools/test_exchange_3coins.py @@ -0,0 +1,134 @@ +import boa +import pytest +from eth_utils import function_signature_to_4byte_selector + +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def token_a(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTA", + "OTA", + 18, + 1006470359024000000, + ) + + +@pytest.fixture(scope="module") +def token_b(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Oracle.vy", + "OTB", + "OTB", + 18, + 1000000000000000000, + ) + + +@pytest.fixture(scope="module") +def token_c(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20.vy", + "OTC", + "OTC", + 18, + ) + + +@pytest.fixture(scope="module") +def pool_tokens(token_a, token_b, token_c): + return [token_a, token_b, token_c] + + +@pytest.fixture(scope="module") +def asset_types(pool_tokens): + _asset_types = [] + for token in pool_tokens: + if "ERC20Oracle" in token.filename: + _asset_types.append(1) + elif "ERC20Rebasing" in token.filename: + _asset_types.append(2) + else: + _asset_types.append(0) + return _asset_types + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + pool_tokens, + zero_address, + amm_interface, + asset_types, + set_pool_implementations, +): + pool_size = len(pool_tokens) + oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + for i in range(pool_size): + + if asset_types[i] == 1: + method_ids[i] = oracle_method_id + oracles[i] = pool_tokens[i].address + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def deposit_amounts(pool_tokens, bob): + _deposit_amounts = [] + for i, token in enumerate(pool_tokens): + _deposit_amount = 10**6 * 10 ** token.decimals() + if token.balanceOf(bob) < _deposit_amount: + mint_for_testing(bob, _deposit_amount, token, False) + + _deposit_amounts.append(_deposit_amount) + return _deposit_amounts + + +@pytest.fixture(scope="module") +def swap(empty_swap, bob, deposit_amounts, pool_tokens): + + for token in pool_tokens: + token.approve(empty_swap, 2**256 - 1, sender=bob) + + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +def test_swap(swap, charlie, pool_tokens): + + amount_in = 10**18 + i = 0 + j = 1 + if amount_in > pool_tokens[i].balanceOf(charlie): + mint_for_testing(charlie, 10**18, pool_tokens[i], False) + + pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + swap.exchange(i, j, amount_in, 0, sender=charlie) From 8c78731ed43c22e6bcdcb5d39b0a7d02f8cb0386 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Sep 2023 18:14:59 +0200 Subject: [PATCH 155/337] fix: views get_D --- contracts/main/CurveStableSwapMetaNG.vy | 4 +-- contracts/main/CurveStableSwapNG.vy | 4 +-- contracts/main/CurveStableSwapNGViews.vy | 11 ++++++-- scripts/deployment_utils.py | 36 ++++++++++++++++++++++-- tests/pools/test_exchange.py | 4 +-- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 2308067d..0b1c13d9 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -405,7 +405,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- if _incoming_coin_asset_type == 2: - assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! + assert _dx > 0 # dev: pool did not receive tokens for swap else: assert dx == _dx # dev: pool did not receive tokens for swap @@ -880,7 +880,7 @@ def remove_liquidity( self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds - log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # TODO: check this! + log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. if _claim_admin_fees: diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 99852843..051de2e8 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -347,7 +347,7 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- if _incoming_coin_asset_type == 2: - assert _dx > 0 # dev: pool did not receive tokens for swap # TODO: Check this!! + assert _dx > 0 # dev: pool did not receive tokens for swap else: assert dx == _dx # dev: pool did not receive tokens for swap @@ -765,7 +765,7 @@ def remove_liquidity( total_supply -= _burn_amount self._burnFrom(msg.sender, _burn_amount) - log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # TODO: check this! + log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. if _claim_admin_fees: diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 8095df31..ec411fb7 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -573,9 +573,16 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256, N_COINS: uint256) -> D: uint256 = S Ann: uint256 = _amp * N_COINS + D_P: uint256 = 0 + Dprev: uint256 = 0 + for i in range(255): - D_P: uint256 = D * D / _xp[0] * D / _xp[1] / pow_mod256(N_COINS, N_COINS) - Dprev: uint256 = D + + D_P = D + for x in _xp: + D_P = D_P * D / (x * N_COINS) + Dprev = D + D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) # Equality with the precision of 1 if D > Dprev: diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 67ae3f4e..2073d5db 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -4,6 +4,7 @@ import click from ape import networks, project from ape.api.address import Address +from eth_utils import function_signature_to_4byte_selector DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -225,7 +226,7 @@ class PoolSettings: "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", # usdt ], 1000, # A - 4000000, # fee + 1000000, # fee 20000000000, # offpeg_fee_multiplier 865, # ma_exp_time 0, # implementation index @@ -233,8 +234,37 @@ class PoolSettings: [0, 0, 0], # method_ids [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles ], - "oracle": [], - "rebasing": [], + "oracles": [ + "WETH<>wstETH", # name + "wstETH-ng", # symbol + [ + "0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6", # wsteth + "0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", # weth + ], + 500, + 1000000, # fee + 20000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + [1, 0], # asset_types + [function_signature_to_4byte_selector("exchangeRate()"), 0], # method_ids + ["0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6", ZERO_ADDRESS], # oracles + ], + "rebasing": [ + "sGNO<>GNO" "sGNO-ng", # name # symbol + [ + "0xA4eF9Da5BA71Cc0D2e5E877a910A37eC43420445", # sGNO + "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb", # GNO + ], + 50, # A, + 1000000, # fee + 20000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + [2, 0], # asset_types + [0, 0], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS], # oracles + ], "meta-plain": [], "meta-oracle": [], "meta-rebasing": [], diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index 924bc041..6df329e0 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -148,8 +148,8 @@ def test_remove_liquidity( ) i += 1 - assert swap.balanceOf(bob) == pytest.approx(deposit_amounts[0] * 2 - withdraw_amount, rel=1.5e-2) - assert swap.totalSupply() == pytest.approx(deposit_amounts[0] * 4 - withdraw_amount, rel=1.5e-2) + assert swap.balanceOf(bob) == pytest.approx(deposit_amounts[0] * pool_size - withdraw_amount, rel=1.5e-2) + assert swap.totalSupply() == pytest.approx(deposit_amounts[0] * 2 * pool_size - withdraw_amount, rel=1.5e-2) def test_remove_imbalanced( self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, deposit_amounts From 02c9ee0e50372ee2ada1b72b720d220e865fe11f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 13 Sep 2023 14:22:19 +0200 Subject: [PATCH 156/337] use dynamic fee instead of base fee --- contracts/main/CurveStableSwapNG.vy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 051de2e8..89d0034f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -711,7 +711,8 @@ def remove_liquidity_imbalance( xs = new_balance + old_balances[i] dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees.append(base_fee * difference / FEE_DENOMINATOR) + fees.append(dynamic_fee * difference / FEE_DENOMINATOR) + self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] From 5207bd1cf5d476652d8071d3a615c5af82fd5ceb Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:33:13 +0200 Subject: [PATCH 157/337] mixbytes auditor comment. fix: gulp while transfer out; but gulp into stored_balances --- contracts/main/CurveStableSwapMetaNG.vy | 6 +++- contracts/main/CurveStableSwapNG.vy | 21 +++++++------ tests/conftest.py | 9 ++++++ tests/pools/test_exchange_received.py | 31 ++++++++++++------- ... => test_specific_liquidity_operations.py} | 12 +++++++ 5 files changed, 57 insertions(+), 22 deletions(-) rename tests/pools/{test_exchange_3coins.py => test_specific_liquidity_operations.py} (88%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 0b1c13d9..7f480da8 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -429,6 +429,10 @@ def _transfer_out( @param receiver Address to send the tokens to """ + # 'gulp' coin balance of the pool to stored_balances here to account for + # donations etc. + coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + # ------------------------- Handle Transfers ----------------------------- assert ERC20(coins[_coin_idx]).transfer( @@ -437,7 +441,7 @@ def _transfer_out( # ----------------------- Update Stored Balances ------------------------- - self.stored_balances[_coin_idx] -= _amount + self.stored_balances[_coin_idx] = coin_balance - _amount # -------------------------- AMM Special Methods ----------------------------- diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 89d0034f..740adc12 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -327,13 +327,13 @@ def _transfer_in( @param expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - _incoming_coin_asset_type: uint8 = asset_types[coin_idx] + _incoming_asset_is_rebasing: bool = asset_types[coin_idx] == 2 # ------------------------- Handle Transfers ----------------------------- if expect_optimistic_transfer: - assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported + assert not _incoming_asset_is_rebasing # dev: rebasing coins not supported _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] else: @@ -346,14 +346,12 @@ def _transfer_in( # --------------------------- Check Transfer ----------------------------- - if _incoming_coin_asset_type == 2: + if _incoming_asset_is_rebasing: assert _dx > 0 # dev: pool did not receive tokens for swap else: assert dx == _dx # dev: pool did not receive tokens for swap - - # ----------------------- Update Stored Balances ------------------------- - - self.stored_balances[coin_idx] += _dx + # Update stored_balances (not for rebasing tokens): + self.stored_balances[coin_idx] += _dx return _dx @@ -369,6 +367,10 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): @param receiver Address to send the tokens to """ + # 'gulp' coin balance of the pool to stored_balances here to account for + # donations etc. + coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + # ------------------------- Handle Transfers ----------------------------- assert ERC20(coins[_coin_idx]).transfer( @@ -377,7 +379,7 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): # ----------------------- Update Stored Balances ------------------------- - self.stored_balances[_coin_idx] -= _amount + self.stored_balances[_coin_idx] = coin_balance - _amount # -------------------------- AMM Special Methods ----------------------------- @@ -753,12 +755,13 @@ def remove_liquidity( amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() + value: uint256 = 0 for i in range(MAX_COINS_128): if i == N_COINS_128: break - value: uint256 = balances[i] * _burn_amount / total_supply + value = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts.append(value) self._transfer_out(i, value, _receiver) diff --git a/tests/conftest.py b/tests/conftest.py index 189f66e1..d543aa3e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -177,6 +177,15 @@ def skip_by_token_type(request, swap): pytest.skip("skipped because no tokens for these types") +@pytest.fixture(autouse=True) +def skip_rebasing(request, swap): + only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") + if only_for_token_types: + asset_types_contains_rebasing = 2 in swap._immutables.asset_types + if asset_types_contains_rebasing: + pytest.skip("skipped because test excludes rebasing tokens") + + # Usage # @pytest.mark.only_for_pool_type(1) # class TestMetaPool... diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index c51b05d2..f1f7b02a 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -105,8 +105,8 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): # TODO: need to permutate/combinate N_COIN combos. -@pytest.mark.only_for_token_types(0, 1) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +@pytest.mark.skip_rebasing_tokens def test_exchange_received_nonrebasing( bob, swap, @@ -124,22 +124,15 @@ def test_exchange_received_nonrebasing( assert swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] == swap_data["amount_out"] -@pytest.mark.only_for_token_types(0, 1) +@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving): with boa.env.prank(bob), boa.reverts(): swap.exchange_received(sending, receiving, 1, 0, bob) -@pytest.mark.only_for_token_types(2) -@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - with boa.reverts(): - transfer_and_swap(swap, sending, receiving, False) - - @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(0, 1) +@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_nonrebasing( bob, @@ -172,7 +165,7 @@ def test_exchange_underlying_received_nonrebasing( @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(0, 1) +@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_not_received(bob, swap, sending, receiving): with boa.env.prank(bob), boa.reverts(): @@ -183,6 +176,20 @@ def test_exchange_underlying_not_received(bob, swap, sending, receiving): @pytest.mark.only_for_token_types(2) @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): - if sending == 0: + + if swap._immutables.asset_types[sending] == 2: with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) + else: + transfer_and_swap(swap, sending, receiving, True) + + +@pytest.mark.only_for_token_types(2) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): + + if swap._immutables.asset_types[sending] == 2: + with boa.reverts(): + transfer_and_swap(swap, sending, receiving, False) + else: + transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/pools/test_exchange_3coins.py b/tests/pools/test_specific_liquidity_operations.py similarity index 88% rename from tests/pools/test_exchange_3coins.py rename to tests/pools/test_specific_liquidity_operations.py index 0c42bb2f..ee19969b 100644 --- a/tests/pools/test_exchange_3coins.py +++ b/tests/pools/test_specific_liquidity_operations.py @@ -132,3 +132,15 @@ def test_swap(swap, charlie, pool_tokens): pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) swap.exchange(i, j, amount_in, 0, sender=charlie) + + +def test_rebase(swap, charlie, bob, pool_tokens): + + amount_rewards = 10**4 * 10**18 + i = 1 + if amount_rewards > pool_tokens[i].balanceOf(charlie): + mint_for_testing(charlie, amount_rewards, pool_tokens[i], False) + + pool_tokens[i].transfer(swap, amount_rewards, sender=charlie) # <---- donate. + bob_lp_tokens = swap.balanceOf(bob) + swap.remove_liquidity(bob_lp_tokens, [0, 0, 0], sender=bob) # <--- should not revert From f376632bd7d24430e62bfeebe57965663dc1a007 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:38:23 +0200 Subject: [PATCH 158/337] fix: use correct dx_w_fee in stored_balances --- contracts/main/CurveStableSwapFactoryNG.vy | 1 - contracts/main/CurveStableSwapMetaNG.vy | 36 ++-- tests/pools/meta/test_meta.py | 183 +++++++++++++++++++++ 3 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 tests/pools/meta/test_meta.py diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index eb722042..ad776fd0 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -614,7 +614,6 @@ def deploy_metapool( assert _fee <= 100000000, "Invalid fee" assert _offpeg_fee_multiplier * _fee <= MAX_FEE * FEE_DENOMINATOR - base_pool_n_coins: uint256 = len(self.base_pool_data[_base_pool].coins) assert base_pool_n_coins != 0, "Base pool is not added" diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 7f480da8..326849d8 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -300,13 +300,14 @@ def __init__( BASE_POOL = _base_pool BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) - coins = _coins + coins = _coins # <---------------- coins[1] is always base pool LP token. rate_multipliers = _rate_multipliers asset_types = _asset_types # contains asset types for all pool tokens including base pool tokens for i in range(MAX_COINS): if i < BASE_N_COINS: - # Approval needed for add_liquidity operation on base pool in _exchange_underlying + # Approval needed for add_liquidity operation on base pool in + # _exchange_underlying: ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) @@ -381,12 +382,14 @@ def _transfer_in( _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) _incoming_coin_asset_type: uint8 = asset_types[coin_metapool_idx] _stored_balance: uint256 = self.stored_balances[coin_metapool_idx] + _input_coin_is_in_base_pool: bool = False - if coin_basepool_idx >= 0 and coin_metapool_idx == 1: # self._exchange_underlying + # Check if _transfer_in is being called by _exchange_underlying: + if coin_basepool_idx >= 0 and coin_metapool_idx == 1: _input_coin = ERC20(BASE_COINS[coin_basepool_idx]) _incoming_coin_asset_type = asset_types[coin_basepool_idx + 2] - _stored_balance = 0 + _input_coin_is_in_base_pool = True _dx: uint256 = _input_coin.balanceOf(self) @@ -395,11 +398,18 @@ def _transfer_in( if expect_optimistic_transfer: assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported - _dx -= _stored_balance # <--- for base_pool coins, stored balance is 0. + if not _input_coin_is_in_base_pool: + _dx -= _stored_balance # <- for base_pool coins, stored balance is 0. + # So, we do not check _stored_balance for incoming base pool tokens. else: - assert _input_coin.transferFrom(sender, self, dx, default_return_value=True) + assert _input_coin.transferFrom( + sender, + self, + dx, + default_return_value=True + ) _dx = _input_coin.balanceOf(self) - _dx # --------------------------- Check Transfer ----------------------------- @@ -409,6 +419,11 @@ def _transfer_in( else: assert dx == _dx # dev: pool did not receive tokens for swap + # ------------ Check if liquidity needs to be added somewhere ------------ + + if _input_coin_is_in_base_pool: + _dx = self._meta_add_liquidity(_dx, coin_basepool_idx) + # ----------------------- Update Stored Balances ------------------------- self.stored_balances[coin_metapool_idx] += _dx @@ -422,8 +437,6 @@ def _transfer_out( ): """ @notice Transfer a single token from the pool to receiver. - @dev This function is called by `remove_liquidity` and - `remove_liquidity_one` and `_exchange` methods. @param _coin Address of the token to transfer out @param _amount Amount of token to transfer out @param receiver Address to send the tokens to @@ -1045,6 +1058,8 @@ def _exchange_underlying( # --------------------------- Do Transfer in ----------------------------- + # If incoming coin is supposed to go to the base pool, the _transfer_in + # method will add_liquidity in the base pool and return dx_w_fee LP tokens dx_w_fee: uint256 = self._transfer_in( meta_i, base_i, @@ -1066,8 +1081,6 @@ def _exchange_underlying( else: - dx_w_fee = self._meta_add_liquidity(dx_w_fee, base_i) - # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) x += xp[MAX_METAPOOL_COIN_INDEX] @@ -1075,7 +1088,6 @@ def _exchange_underlying( dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) # Adjust stored balances of meta-level tokens: - # TODO: refactor this stray self.stored_balances to self._transfer_out somehow self.stored_balances[meta_j] -= dy # Withdraw from the base pool if needed @@ -1098,7 +1110,7 @@ def _exchange_underlying( # ------------------------------------------------------------------------ - log TokenExchangeUnderlying(sender, i, _dx, j, dy) # TODO: check this! + log TokenExchangeUnderlying(sender, i, _dx, j, dy) return dy diff --git a/tests/pools/meta/test_meta.py b/tests/pools/meta/test_meta.py new file mode 100644 index 00000000..db08d109 --- /dev/null +++ b/tests/pools/meta/test_meta.py @@ -0,0 +1,183 @@ +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def base_pool_decimals(): + return [18, 18, 18] + + +@pytest.fixture(scope="module") +def base_pool_tokens(deployer, base_pool_decimals): + tokens = [] + with boa.env.prank(deployer): + tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", base_pool_decimals[2])) + + return tokens + + +@pytest.fixture(scope="module") +def meta_token(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20.vy", + "OTA", + "OTA", + 18, + ) + + +@pytest.fixture(scope="module") +def asset_types(): + _asset_types = [0, 0, 0] + return _asset_types + + +@pytest.fixture(scope="module") +def base_pool( + deployer, + factory, + base_pool_tokens, + zero_address, + amm_interface, + asset_types, + set_pool_implementations, +): + pool_size = len(base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def metapool_tokens(meta_token, base_pool): + return [meta_token, base_pool] + + +@pytest.fixture(scope="module") +def add_base_pool( + owner, + factory, + base_pool, + base_pool_tokens, +): + with boa.env.prank(owner): + factory.add_base_pool( + base_pool.address, + base_pool.address, + [t.address for t in base_pool_tokens], + [0] * len(base_pool_tokens), + len(base_pool_tokens), + ) + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + base_pool, + amm_interface_meta, + asset_types, + add_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + asset_type = meta_token.asset_type() + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return amm_interface_meta.at(pool) + + +@pytest.fixture(scope="module") +def deposit_amounts(meta_token, bob, base_pool, base_pool_tokens, base_pool_decimals, empty_swap): + _deposit_amounts = [] + INITIAL_AMOUNT = 3_000_000 + + _deposit_amount = INITIAL_AMOUNT // 3 * 10 ** meta_token.decimals() + if meta_token.balanceOf(bob) < _deposit_amount: + mint_for_testing(bob, _deposit_amount, meta_token, False) + _deposit_amounts.append(_deposit_amount) + + def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = INITIAL_AMOUNT // 3 + with boa.env.prank(user): + for d, token in zip(base_pool_decimals, base_pool_tokens): + token._mint_for_testing(user, amount * 10**d) + token.approve(base_pool.address, 2**256 - 1) + + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) + + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + _deposit_amounts.append(INITIAL_AMOUNT // 3 * 10 ** base_pool.decimals()) + + return _deposit_amounts + + +@pytest.fixture(scope="module") +def swap(empty_swap, bob, deposit_amounts, metapool_tokens): + for token in metapool_tokens: + token.approve(empty_swap.address, 2**256 - 1, sender=bob) + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +def test_exchange(swap, charlie, meta_token): + amount = 1000 * 10**18 + mint_for_testing(charlie, amount, meta_token, False) + meta_token.approve(swap.address, 2**256 - 1, sender=charlie) + swap.exchange(0, 1, amount, 0, sender=charlie) + + +def test_exchange_underlying(swap, charlie, meta_token, base_pool_tokens): + + base_pool_index = 0 + amount = 1000 * 10**18 + mint_for_testing(charlie, amount, meta_token, False) + mint_for_testing(charlie, amount, base_pool_tokens[base_pool_index], False) + + swap.exchange_underlying(1, 0) From ef17510b5722db04cd6a19cdea8353338148f10b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 15 Sep 2023 18:58:32 +0200 Subject: [PATCH 159/337] fix paranthesis --- .gitignore | 1 + contracts/main/CurveStableSwapNGViews.vy | 8 +++++- tests/conftest.py | 18 ++++++++++++ tests/pools/meta/test_exchange_underlying.py | 29 ++++++++++++++------ tests/pools/rebasing/__init__.py | 0 5 files changed, 47 insertions(+), 9 deletions(-) delete mode 100644 tests/pools/rebasing/__init__.py diff --git a/.gitignore b/.gitignore index 7c26134f..e234fab5 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,4 @@ node_modules docs/ scripts/experiments/get_p.py .python-version +todo.txt diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index ec411fb7..421ab502 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -172,18 +172,24 @@ def get_dy_underlying( meta_j = 1 if i == 0: - x = xp[i] + dx * (rates[0] / 10**18) + + x = xp[i] + dx * rates[0] / 10**18 + else: + if j == 0: + # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() x = self._base_calc_token_amounts( dx, base_i, rates[1], base_n_coins, BASE_POOL, True ) + # Accounting for deposit/withdraw fees approximately x -= x * StableSwapNG(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) # Adding number of pool tokens x += xp[MAX_COIN] + else: # If both are from the base pool return StableSwapNG(BASE_POOL).get_dy(base_i, base_j, dx) diff --git a/tests/conftest.py b/tests/conftest.py index d543aa3e..e031c0c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -186,6 +186,24 @@ def skip_rebasing(request, swap): pytest.skip("skipped because test excludes rebasing tokens") +@pytest.fixture(autouse=True) +def skip_oracle(request, swap): + only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") + if only_for_token_types: + asset_types_contains_oracle = 1 in swap._immutables.asset_types + if asset_types_contains_oracle: + pytest.skip("skipped because test excludes oraclised tokens") + + +@pytest.fixture(autouse=True) +def only_oracle(request, swap): + only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") + if only_for_token_types: + asset_types_contains_rebasing = 1 in swap._immutables.asset_types + if not asset_types_contains_rebasing: + pytest.skip("skipped because test excludes oraclised tokens") + + # Usage # @pytest.mark.only_for_pool_type(1) # class TestMetaPool... diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 595eb18d..bdc58686 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -9,23 +9,34 @@ @pytest.mark.only_for_pool_type(1) # only for metapools class TestMetaExchangeUnderlying: @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) + @pytest.mark.only_oracle_tokens def test_amounts(self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): + underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10 ** underlying_decimals[sending] + amount_sent = 10 ** underlying_decimals[sending] + + if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: + underlying_tokens[sending]._mint_for_testing(bob, amount_sent) - if sending > 0: - underlying_tokens[sending]._mint_for_testing(bob, amount) + initial_swap_balances = [swap.balances(0), swap.balances(1)] # noqa: F841 + expected_received = swap.get_dy_underlying(sending, receiving, amount_sent) + expected_ratio_received_sent = expected_received / amount_sent # noqa: F841 - initial_amount = underlying_tokens[sending].balanceOf(bob) + initial_amount_sending = underlying_tokens[sending].balanceOf(bob) initial_amount_receiving = underlying_tokens[receiving].balanceOf(bob) - swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) - assert underlying_tokens[sending].balanceOf(bob) == initial_amount - amount + received_true = swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 + swap_logs = swap.get_logs() # noqa: F841 + assert underlying_tokens[sending].balanceOf(bob) == initial_amount_sending - amount_sent + + amount_received = underlying_tokens[receiving].balanceOf(bob) - initial_amount_receiving + swap_balances = [swap.balances(0), swap.balances(1)] # noqa: F841 - received = underlying_tokens[receiving].balanceOf(bob) - initial_amount_receiving - assert 0.999 <= received / amount < 1 + assert 0.999 <= amount_received / amount_sent < 1 + @pytest.mark.skip_rebasing_tokens + @pytest.mark.skip_oracle_tokens @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) def test_fees( self, @@ -52,6 +63,8 @@ def test_fees( expected = expected * 10**18 // base_pool.get_virtual_price() assert expected / admin_fee == approx(1, rel=1e-3) + @pytest.mark.skip_rebasing_tokens + @pytest.mark.skip_oracle_tokens @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) def test_min_dy_underlying( self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals diff --git a/tests/pools/rebasing/__init__.py b/tests/pools/rebasing/__init__.py deleted file mode 100644 index e69de29b..00000000 From 0c87415e53b775fa33d85c21d3e6ea45ec7e2b94 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:57:01 +0200 Subject: [PATCH 160/337] add D_ma_time for D_oracle --- contracts/main/CurveStableSwapMetaNG.vy | 24 +++++++++++++++--------- contracts/main/CurveStableSwapNG.vy | 24 +++++++++++++++--------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 326849d8..2af0d488 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -216,6 +216,7 @@ oracles: DynArray[uint256, MAX_COINS] last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) +D_ma_time: public(uint256) ma_last_time: public(uint256) # shift(2**32 - 1, 224) @@ -324,6 +325,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time + self.D_ma_time = 62324 # <--------- 12 hours default on contract start. self.ma_last_time = block.timestamp for i in range(N_COINS_128): @@ -1319,7 +1321,10 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # Upate packed prices ----------------- last_prices_packed_new[0] = self.pack_prices( spot_price[0], - self._calc_moving_average(last_prices_packed_current[0]) + self._calc_moving_average( + last_prices_packed_current[0], + self.ma_exp_time + ) ) self.last_prices_packed = last_prices_packed_new @@ -1329,7 +1334,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): last_D_packed_current: uint256 = self.last_D_packed self.last_D_packed = self.pack_prices( D, - self._calc_moving_average(last_D_packed_current) + self._calc_moving_average(last_D_packed_current, self.D_ma_time) ) # Housekeeping: Update ma_last_time ------------------ @@ -1339,7 +1344,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): @internal @view -def _calc_moving_average(packed_value: uint256) -> uint256: +def _calc_moving_average(packed_value: uint256, averaging_window: uint256) -> uint256: ma_last_time: uint256 = self.ma_last_time last_spot_value: uint256 = packed_value & (2**128 - 1) @@ -1348,7 +1353,7 @@ def _calc_moving_average(packed_value: uint256) -> uint256: if ma_last_time < block.timestamp: # calculate new_ema_value and return that. alpha: uint256 = math.exp( -convert( - (block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256 + (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 ) ) return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 @@ -1392,14 +1397,14 @@ def get_p(i: uint256) -> uint256: @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: assert i == 0 # dev: metapools do not have price oracle indices greater than 0. - return self._calc_moving_average(self.last_prices_packed[0]) + return self._calc_moving_average(self.last_prices_packed[0], self.ma_exp_time) @external @view @nonreentrant('lock') def D_oracle() -> uint256: - return self._calc_moving_average(self.last_D_packed) + return self._calc_moving_average(self.last_D_packed, self.D_ma_time) # ---------------------------- ERC20 Utils ----------------------------------- @@ -1756,12 +1761,13 @@ def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): @external -def set_ma_exp_time(_ma_exp_time: uint256): +def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): """ - @notice Set the moving average window of the price oracle. + @notice Set the moving average window of the price oracles. @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ assert msg.sender == factory.admin() # dev: only owner - assert _ma_exp_time != 0 + assert 0 not in [_ma_exp_time, _D_ma_time] self.ma_exp_time = _ma_exp_time + self.D_ma_time = _D_ma_time diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 740adc12..cca80fcd 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -172,6 +172,7 @@ oracles: DynArray[uint256, MAX_COINS] last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) +D_ma_time: public(uint256) ma_last_time: public(uint256) # shift(2**32 - 1, 224) @@ -267,6 +268,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time + self.D_ma_time = 62324 # <--------- 12 hours default on contract start. self.ma_last_time = block.timestamp for i in range(MAX_COINS_128): @@ -1265,7 +1267,10 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # Upate packed prices ----------------- last_prices_packed_new[i] = self.pack_prices( spot_price[i], - self._calc_moving_average(last_prices_packed_current[i]) + self._calc_moving_average( + last_prices_packed_current[i], + self.ma_exp_time + ) ) self.last_prices_packed = last_prices_packed_new @@ -1275,7 +1280,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): last_D_packed_current: uint256 = self.last_D_packed self.last_D_packed = self.pack_prices( D, - self._calc_moving_average(last_D_packed_current) + self._calc_moving_average(last_D_packed_current, self.D_ma_time) ) # Housekeeping: Update ma_last_time ------------------ @@ -1285,7 +1290,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): @internal @view -def _calc_moving_average(packed_value: uint256) -> uint256: +def _calc_moving_average(packed_value: uint256, averaging_window: uint256) -> uint256: ma_last_time: uint256 = self.ma_last_time last_spot_value: uint256 = packed_value & (2**128 - 1) @@ -1294,7 +1299,7 @@ def _calc_moving_average(packed_value: uint256) -> uint256: if ma_last_time < block.timestamp: # calculate new_ema_value and return that. alpha: uint256 = self.exp( -convert( - (block.timestamp - ma_last_time) * 10**18 / self.ma_exp_time, int256 + (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 ) ) return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 @@ -1335,14 +1340,14 @@ def get_p(i: uint256) -> uint256: @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - return self._calc_moving_average(self.last_prices_packed[i]) + return self._calc_moving_average(self.last_prices_packed[i], self.ma_exp_time) @external @view @nonreentrant('lock') def D_oracle() -> uint256: - return self._calc_moving_average(self.last_D_packed) + return self._calc_moving_average(self.last_D_packed, self.D_ma_time) # ----------------------------- Math Utils ----------------------------------- @@ -1772,12 +1777,13 @@ def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): @external -def set_ma_exp_time(_ma_exp_time: uint256): +def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): """ - @notice Set the moving average window of the price oracle. + @notice Set the moving average window of the price oracles. @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ assert msg.sender == factory.admin() # dev: only owner - assert _ma_exp_time != 0 + assert 0 not in [_ma_exp_time, _D_ma_time] self.ma_exp_time = _ma_exp_time + self.D_ma_time = _D_ma_time From 6b7dbdf437fd94d072e1fdd10e84bb6c02296f29 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:55:44 +0200 Subject: [PATCH 161/337] do not add liquidity to base pool and incr ement stored_balances if it is a base_pool swap --- contracts/main/CurveStableSwapMetaNG.vy | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 2af0d488..a401b6ff 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -371,6 +371,7 @@ def _transfer_in( sender: address, receiver: address, expect_optimistic_transfer: bool, + is_base_pool_swap: bool = False, ) -> uint256: """ @notice Contains all logic to handle ERC20 token transfers. @@ -380,6 +381,8 @@ def _transfer_in( @param sender address to transfer `_coin` from. @param receiver address to transfer `_coin` to. @param expect_optimistic_transfer True if contract expects an optimistic coin transfer + @param is_base_pool_swap Default is set to False. + @return amount of coins received """ _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) _incoming_coin_asset_type: uint8 = asset_types[coin_metapool_idx] @@ -424,6 +427,13 @@ def _transfer_in( # ------------ Check if liquidity needs to be added somewhere ------------ if _input_coin_is_in_base_pool: + if is_base_pool_swap: + return _dx # <----- _exchange_underlying: all input goes to swap. + # So, we will not increment self.stored_balances for metapool_idx. + + # Swap involves base <> meta pool interaction. Add incoming base pool + # token to the base pool, mint _dx base pool LP token (idx 1) and add + # that to self.stored_balances and return that instead. _dx = self._meta_add_liquidity(_dx, coin_basepool_idx) # ----------------------- Update Stored Balances ------------------------- @@ -1070,6 +1080,7 @@ def _exchange_underlying( sender, receiver, expect_optimistic_transfer, + (i > 0 and j > 0), # <--- if True: do not add liquidity to base pool. ) # ------------------------------- Exchange ------------------------------- From 8992bea992aa78d36f001db142f95926a830f426 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:15:24 +0200 Subject: [PATCH 162/337] fix: incorrect stored_balances update; fix: remove donation for non rebasing; fix: remove exchange_received for pools containing rebasing tokens; fix: remove unnecessary inputs in _transfer_in for metapools; fix: disallow base pools which contain rebasing tokens --- .gitignore | 1 + contracts/main/CurveStableSwapFactoryNG.vy | 4 +- contracts/main/CurveStableSwapMetaNG.vy | 42 ++--- contracts/main/CurveStableSwapNG.vy | 26 +-- tests/conftest.py | 13 +- tests/pools/meta/test_meta_new_base.py | 191 +++++++++++++++++++++ tests/pools/test_exchange_received.py | 44 ++++- 7 files changed, 275 insertions(+), 46 deletions(-) create mode 100644 tests/pools/meta/test_meta_new_base.py diff --git a/.gitignore b/.gitignore index e234fab5..be85aaec 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ docs/ scripts/experiments/get_p.py .python-version todo.txt +AuditorComments.md diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index ad776fd0..3e7fcf67 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -724,11 +724,13 @@ def add_base_pool( ): """ @notice Add a base pool to the registry, which may be used in factory metapools - @dev Only callable by admin + @dev 1. Only callable by admin + 2. Rebasing tokens are not allowed in the base pool. @param _base_pool Pool address to add @param _asset_types Asset type for pool, as an integer """ assert msg.sender == self.admin # dev: admin-only function + assert 2 not in _asset_types # dev: rebasing tokens cannot be in base pool assert len(self.base_pool_data[_base_pool].coins) == 0 # dev: pool exists assert _n_coins < MAX_COINS # dev: base pool can only have (MAX_COINS - 1) coins. diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index a401b6ff..5a431ae2 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -174,6 +174,8 @@ N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 +POOL_IS_REBASING_IMPLEMENTATION: immutable(bool) + BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) @@ -305,6 +307,8 @@ def __init__( rate_multipliers = _rate_multipliers asset_types = _asset_types # contains asset types for all pool tokens including base pool tokens + POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types + for i in range(MAX_COINS): if i < BASE_N_COINS: # Approval needed for add_liquidity operation on base pool in @@ -367,9 +371,7 @@ def _transfer_in( coin_metapool_idx: int128, coin_basepool_idx: int128, dx: uint256, - dy: uint256, sender: address, - receiver: address, expect_optimistic_transfer: bool, is_base_pool_swap: bool = False, ) -> uint256: @@ -377,15 +379,12 @@ def _transfer_in( @notice Contains all logic to handle ERC20 token transfers. @param _coin address of the coin to transfer in. @param dx amount of `_coin` to transfer into the pool. - @param dy amount of `_coin` to transfer out of the pool. @param sender address to transfer `_coin` from. - @param receiver address to transfer `_coin` to. @param expect_optimistic_transfer True if contract expects an optimistic coin transfer @param is_base_pool_swap Default is set to False. @return amount of coins received """ _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) - _incoming_coin_asset_type: uint8 = asset_types[coin_metapool_idx] _stored_balance: uint256 = self.stored_balances[coin_metapool_idx] _input_coin_is_in_base_pool: bool = False @@ -393,7 +392,6 @@ def _transfer_in( if coin_basepool_idx >= 0 and coin_metapool_idx == 1: _input_coin = ERC20(BASE_COINS[coin_basepool_idx]) - _incoming_coin_asset_type = asset_types[coin_basepool_idx + 2] _input_coin_is_in_base_pool = True _dx: uint256 = _input_coin.balanceOf(self) @@ -402,10 +400,9 @@ def _transfer_in( if expect_optimistic_transfer: - assert _incoming_coin_asset_type != 2 # dev: rebasing coins not supported if not _input_coin_is_in_base_pool: - _dx -= _stored_balance # <- for base_pool coins, stored balance is 0. - # So, we do not check _stored_balance for incoming base pool tokens. + _dx = _dx - _stored_balance + assert _dx >= dx # dev: pool did not receive tokens for swap else: @@ -417,13 +414,6 @@ def _transfer_in( ) _dx = _input_coin.balanceOf(self) - _dx - # --------------------------- Check Transfer ----------------------------- - - if _incoming_coin_asset_type == 2: - assert _dx > 0 # dev: pool did not receive tokens for swap - else: - assert dx == _dx # dev: pool did not receive tokens for swap - # ------------ Check if liquidity needs to be added somewhere ------------ if _input_coin_is_in_base_pool: @@ -518,8 +508,16 @@ def _balances() -> DynArray[uint256, MAX_COINS]: @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances_i: uint256 = 0 + for i in range(N_COINS_128): - result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) + + if POOL_IS_REBASING_IMPLEMENTATION: + balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + else: + balances_i = self.stored_balances[i] - self.admin_balances[i] + + result.append(balances_i) return result @@ -578,6 +576,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ + assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, @@ -681,9 +680,7 @@ def add_liquidity( i, -1, # <--- we're not handling underlying coins here _amounts[i], - 0, msg.sender, - empty(address), False, # expect_optimistic_transfer ) @@ -1003,9 +1000,7 @@ def _exchange( i, -1, # <----- we're not handling underlying coins here. _dx, - _min_dy, sender, - receiver, expect_optimistic_transfer ) @@ -1076,9 +1071,7 @@ def _exchange_underlying( meta_i, base_i, _dx, - _min_dy, sender, - receiver, expect_optimistic_transfer, (i > 0 and j > 0), # <--- if True: do not add liquidity to base pool. ) @@ -1094,6 +1087,9 @@ def _exchange_underlying( else: + # dx_w_fee is the number of base_pool LP tokens minted after + # base_pool.add_liquidity in self._transfer_in + # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) x += xp[MAX_METAPOOL_COIN_INDEX] diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index cca80fcd..e352dd42 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -135,6 +135,8 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 +POOL_IS_REBASING_IMPLEMENTATION: immutable(bool) + factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] @@ -257,6 +259,7 @@ def __init__( rate_multipliers = _rate_multipliers asset_types = _asset_types + POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types factory = Factory(msg.sender) @@ -329,14 +332,13 @@ def _transfer_in( @param expect_optimistic_transfer True if contract expects an optimistic coin transfer """ _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - _incoming_asset_is_rebasing: bool = asset_types[coin_idx] == 2 # ------------------------- Handle Transfers ----------------------------- if expect_optimistic_transfer: - assert not _incoming_asset_is_rebasing # dev: rebasing coins not supported - _dx = ERC20(coins[coin_idx]).balanceOf(self) - self.stored_balances[coin_idx] + _dx = _dx - self.stored_balances[coin_idx] + assert _dx >= dx else: @@ -346,14 +348,9 @@ def _transfer_in( _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx - # --------------------------- Check Transfer ----------------------------- + # --------------------------- Store transferred in amount --------------------------- - if _incoming_asset_is_rebasing: - assert _dx > 0 # dev: pool did not receive tokens for swap - else: - assert dx == _dx # dev: pool did not receive tokens for swap - # Update stored_balances (not for rebasing tokens): - self.stored_balances[coin_idx] += _dx + self.stored_balances[coin_idx] += _dx return _dx @@ -429,13 +426,19 @@ def _balances() -> DynArray[uint256, MAX_COINS]: @dev This method ensures LPs keep all rebases and admin only claims swap fees. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances_i: uint256 = 0 for i in range(MAX_COINS_128): if i == N_COINS_128: break - result.append(ERC20(coins[i]).balanceOf(self) - self.admin_balances[i]) + if POOL_IS_REBASING_IMPLEMENTATION: + balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + else: + balances_i = self.stored_balances[i] - self.admin_balances[i] + + result.append(balances_i) return result @@ -494,6 +497,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ + assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, diff --git a/tests/conftest.py b/tests/conftest.py index e031c0c7..1787acfc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -183,7 +183,7 @@ def skip_rebasing(request, swap): if only_for_token_types: asset_types_contains_rebasing = 2 in swap._immutables.asset_types if asset_types_contains_rebasing: - pytest.skip("skipped because test excludes rebasing tokens") + pytest.skip("skipped because test includes rebasing tokens") @pytest.fixture(autouse=True) @@ -192,7 +192,7 @@ def skip_oracle(request, swap): if only_for_token_types: asset_types_contains_oracle = 1 in swap._immutables.asset_types if asset_types_contains_oracle: - pytest.skip("skipped because test excludes oraclised tokens") + pytest.skip("skipped because test includes oraclised tokens") @pytest.fixture(autouse=True) @@ -204,6 +204,15 @@ def only_oracle(request, swap): pytest.skip("skipped because test excludes oraclised tokens") +@pytest.fixture(autouse=True) +def only_rebasing(request, swap): + marker = request.node.get_closest_marker("contains_rebasing_tokens") + if marker: + asset_types_contains_rebasing = 2 in swap._immutables.asset_types + if not asset_types_contains_rebasing: + pytest.skip("skipped because test excludes rebasing tokens") + + # Usage # @pytest.mark.only_for_pool_type(1) # class TestMetaPool... diff --git a/tests/pools/meta/test_meta_new_base.py b/tests/pools/meta/test_meta_new_base.py new file mode 100644 index 00000000..4c5f506b --- /dev/null +++ b/tests/pools/meta/test_meta_new_base.py @@ -0,0 +1,191 @@ +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def base_pool_decimals(): + return [18, 18] + + +@pytest.fixture(scope="module") +def base_pool_tokens(deployer, base_pool_decimals): + tokens = [] + with boa.env.prank(deployer): + tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) + + return tokens + + +@pytest.fixture(scope="module") +def meta_token(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20.vy", + "OTA", + "OTA", + 18, + ) + + +@pytest.fixture(scope="module") +def asset_types(): + _asset_types = [0, 0] + return _asset_types + + +@pytest.fixture(scope="module") +def base_pool( + deployer, + factory, + base_pool_tokens, + zero_address, + amm_interface, + asset_types, + set_pool_implementations, +): + pool_size = len(base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def metapool_tokens(meta_token, base_pool): + return [meta_token, base_pool] + + +@pytest.fixture(scope="module") +def add_base_pool( + owner, + factory, + base_pool, + base_pool_tokens, +): + with boa.env.prank(owner): + factory.add_base_pool( + base_pool.address, + base_pool.address, + [t.address for t in base_pool_tokens], + [0] * len(base_pool_tokens), + len(base_pool_tokens), + ) + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + base_pool, + amm_interface_meta, + asset_types, + add_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + asset_type = meta_token.asset_type() + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return amm_interface_meta.at(pool) + + +@pytest.fixture(scope="module") +def deposit_amounts(meta_token, bob, base_pool, base_pool_tokens, base_pool_decimals, empty_swap): + _deposit_amounts = [] + INITIAL_AMOUNT = 3_000_000 + + _deposit_amount = INITIAL_AMOUNT // 3 * 10 ** meta_token.decimals() + if meta_token.balanceOf(bob) < _deposit_amount: + mint_for_testing(bob, _deposit_amount, meta_token, False) + _deposit_amounts.append(_deposit_amount) + + def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = INITIAL_AMOUNT // 3 + with boa.env.prank(user): + for d, token in zip(base_pool_decimals, base_pool_tokens): + token._mint_for_testing(user, amount * 10**d) + token.approve(base_pool.address, 2**256 - 1) + + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) + + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + _deposit_amounts.append(INITIAL_AMOUNT // 3 * 10 ** base_pool.decimals()) + + return _deposit_amounts + + +@pytest.fixture(scope="module") +def swap(empty_swap, bob, deposit_amounts, metapool_tokens): + for token in metapool_tokens: + token.approve(empty_swap.address, 2**256 - 1, sender=bob) + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +@pytest.mark.only_for_pool_type(1) +def test_donation_exchange(swap, bob, base_pool, base_pool_tokens): + amount_rewards = 19 * 10**6 * 10**18 + + i = 1 + j = 0 + if amount_rewards > base_pool_tokens[i].balanceOf(bob): + mint_for_testing(bob, amount_rewards, base_pool_tokens[i], False) + + donation_amount = 10**6 * 10**18 + imbalance_liq_removed = 500_000 * 10**18 + + D = base_pool.get_virtual_price() + base_pool_tokens[i].transfer(base_pool, donation_amount, sender=bob) + assert base_pool.get_virtual_price() == D # donation does not change vprice + + bob_meta_lp_balance = swap.balanceOf(bob) + swap.remove_liquidity_imbalance([imbalance_liq_removed, 0], bob_meta_lp_balance, sender=bob) + bob_meta_lp_balance_after = swap.balanceOf(bob) + + received = base_pool.exchange_received(i, j, donation_amount, 0, bob, sender=bob) + + profit = imbalance_liq_removed + (received - donation_amount) + (bob_meta_lp_balance_after - bob_meta_lp_balance) + assert profit < 0 diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index f1f7b02a..76369467 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -104,7 +104,6 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): return _transfer_and_swap -# TODO: need to permutate/combinate N_COIN combos. @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) @pytest.mark.skip_rebasing_tokens def test_exchange_received_nonrebasing( @@ -164,6 +163,37 @@ def test_exchange_underlying_received_nonrebasing( ) +@pytest.mark.skip_rebasing_tokens +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, receiving, transfer_and_swap): + + mint_for_testing(bob, 1, pool_tokens[sending], False) + pool_tokens[sending].transfer(swap, 1, sender=bob) + + mint_for_testing(charlie, 10**18, pool_tokens[sending], False) + transfer_and_swap(swap, sending, receiving, False) + + +@pytest.mark.only_for_pool_type(1) # only for metapools +@pytest.mark.skip_rebasing_tokens +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_exchange_underlying_received_no_dos( + bob, charlie, swap, underlying_tokens, sending, receiving, transfer_and_swap +): + + if sending == 0: + input_coin = underlying_tokens[0] + else: + base_i = sending - 1 + input_coin = underlying_tokens[2 + base_i] + + mint_for_testing(bob, 1, input_coin, False) + input_coin.transfer(swap, 1, sender=bob) + + mint_for_testing(charlie, 10**18, input_coin, False) + transfer_and_swap(swap, sending, receiving, True) + + @pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) @@ -173,23 +203,19 @@ def test_exchange_underlying_not_received(bob, swap, sending, receiving): @pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.only_for_token_types(2) +@pytest.mark.contains_rebasing_tokens @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): - if swap._immutables.asset_types[sending] == 2: + if 2 in swap._immutables.asset_types: with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) - else: - transfer_and_swap(swap, sending, receiving, True) -@pytest.mark.only_for_token_types(2) +@pytest.mark.contains_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - if swap._immutables.asset_types[sending] == 2: + if 2 in swap._immutables.asset_types: with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) - else: - transfer_and_swap(swap, sending, receiving, False) From 2c10cb349ae25109525b3a4dd6de512e418c6e80 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:43:28 +0200 Subject: [PATCH 163/337] fix: also restrict exchange_underlying_received --- contracts/main/CurveStableSwapMetaNG.vy | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 5a431ae2..c3b6596a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -635,6 +635,7 @@ def exchange_underlying_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ + assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange_underlying( msg.sender, i, From 79615f8d27b6002300980e2321aa6b1f7bf4c6fb Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:33:39 +0200 Subject: [PATCH 164/337] fix pipeline --- .github/workflows/test_factory.yaml | 2 +- .github/workflows/test_gauge.yaml | 2 +- .github/workflows/test_pools_2.yaml | 2 +- .github/workflows/test_token.yaml | 2 +- pyproject.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_factory.yaml b/.github/workflows/test_factory.yaml index 36bccbe5..ed7eca94 100644 --- a/.github/workflows/test_factory.yaml +++ b/.github/workflows/test_factory.yaml @@ -36,7 +36,7 @@ jobs: run: | pip install poetry==1.5.1 poetry config virtualenvs.in-project true - poetry install --no-interaction + poetry install --no-interaction --without dev - name: Run Tests Basic run: | diff --git a/.github/workflows/test_gauge.yaml b/.github/workflows/test_gauge.yaml index 0bd5e869..fbb3f03c 100644 --- a/.github/workflows/test_gauge.yaml +++ b/.github/workflows/test_gauge.yaml @@ -36,7 +36,7 @@ jobs: run: | pip install poetry==1.5.1 poetry config virtualenvs.in-project true - poetry install --no-interaction + poetry install --no-interaction --without dev - name: Run Tests run: | diff --git a/.github/workflows/test_pools_2.yaml b/.github/workflows/test_pools_2.yaml index a07f0827..ca96f01b 100644 --- a/.github/workflows/test_pools_2.yaml +++ b/.github/workflows/test_pools_2.yaml @@ -28,7 +28,7 @@ jobs: run: | pip install poetry==1.5.1 poetry config virtualenvs.in-project true - poetry install --no-interaction + poetry install --no-interaction --without dev - name: Run All Token Tests 18,18 run: | diff --git a/.github/workflows/test_token.yaml b/.github/workflows/test_token.yaml index 6abef501..49bd91fe 100644 --- a/.github/workflows/test_token.yaml +++ b/.github/workflows/test_token.yaml @@ -27,7 +27,7 @@ jobs: run: | pip install poetry==1.5.1 poetry config virtualenvs.in-project true - poetry install --no-interaction + poetry install --no-interaction --without dev - name: Run Tests run: | diff --git a/pyproject.toml b/pyproject.toml index 38f3fadf..298cda6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,13 +14,13 @@ titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "e29a7064 vyper = "^0.3.9" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" -eth-ape = "^0.6.18" [tool.poetry.group.dev.dependencies] black = "22.3.0" flake8 = "4.0.1" isort = "5.12.0" mamushi = "^0.0.2a1" +eth-ape = "^0.6.18" [tool.poetry.group.testing.dependencies] From cc8cfb2c90b2cce581e47aafc44e294445f046e0 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:37:07 +0200 Subject: [PATCH 165/337] fix mock contract --- contracts/mocks/CurveTokenV3.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mocks/CurveTokenV3.vy b/contracts/mocks/CurveTokenV3.vy index d135bd24..d9541355 100644 --- a/contracts/mocks/CurveTokenV3.vy +++ b/contracts/mocks/CurveTokenV3.vy @@ -85,7 +85,7 @@ def transferFrom(_from : address, _to : address, _value : uint256) -> bool: self.balanceOf[_to] += _value _allowance: uint256 = self.allowance[_from][msg.sender] - if _allowance != MAX_UINT256: + if _allowance != max_value(uint256): self.allowance[_from][msg.sender] = _allowance - _value log Transfer(_from, _to, _value) From 86462096e40a878faee369839eb27fcd10e112f5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:20:43 +0200 Subject: [PATCH 166/337] fix: dynamic fee; feat: introduce gauge manager; feat: add optional reward token epoch --- contracts/main/CurveStableSwapMetaNG.vy | 10 +++-- contracts/main/CurveStableSwapNG.vy | 8 +--- contracts/main/LiquidityGauge.vy | 54 +++++++++++++------------ tests/pools/test_donation_get_D.py | 34 ++++++++++++++++ 4 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 tests/pools/test_donation_get_D.py diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c3b6596a..902b3dc7 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -505,7 +505,11 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: def _balances() -> DynArray[uint256, MAX_COINS]: """ @notice Calculates the pool's balances _excluding_ the admin's balances. - @dev This method ensures LPs keep all rebases and admin only claims swap fees. + @dev If the pool contains rebasing tokens, this method ensures LPs keep all + rebases and admin only claims swap fees. This also means that, since + admin's balances are stored in an array and not inferred from read balances, + the fees in the rebasing token that the admin collects is immune to + slashing events. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances_i: uint256 = 0 @@ -724,7 +728,7 @@ def add_liquidity( difference = new_balance - ideal_balance # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR - xs = old_balances[i] + new_balance + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) fees.append( unsafe_div( @@ -851,7 +855,7 @@ def remove_liquidity_imbalance( difference = new_balance - ideal_balance # base_fee * difference / FEE_DENOMINATOR - xs = new_balance + old_balances[i] + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) dynamic_fee = self._dynamic_fee(xs, ys, base_fee) fees.append(unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR)) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index e352dd42..7c89c3f4 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -594,7 +594,7 @@ def add_liquidity( difference = new_balance - ideal_balance # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR - xs = old_balances[i] + new_balance + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) fees.append(_dynamic_fee_i * difference / FEE_DENOMINATOR) self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR @@ -717,7 +717,7 @@ def remove_liquidity_imbalance( else: difference = new_balance - ideal_balance - xs = new_balance + old_balances[i] + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) dynamic_fee = self._dynamic_fee(xs, ys, base_fee) fees.append(dynamic_fee * difference / FEE_DENOMINATOR) @@ -1123,14 +1123,10 @@ def _xp_mem( ) -> DynArray[uint256, MAX_COINS]: result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - if i == N_COINS_128: break - result.append(_rates[i] * _balances[i] / PRECISION) - return result diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index bf4347b6..ed3dfdb0 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -61,6 +61,10 @@ event CommitOwnership: event ApplyOwnership: admin: address +event SetGaugeManager: + _gauge_manager: address + + event Transfer: _from: indexed(address) _to: indexed(address) @@ -117,6 +121,7 @@ nonces: public(HashMap[address, uint256]) # Gauge factory: public(address) +manager: public(address) lp_token: public(address) is_killed: public(bool) @@ -170,6 +175,7 @@ def __init__(_lp_token: address): self.lp_token = _lp_token self.factory = msg.sender + self.manager = msg.sender symbol: String[32] = ERC20Extended(_lp_token).symbol() name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") @@ -333,17 +339,7 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r if total_claimable > 0: total_claimed: uint256 = claim_data % 2**128 if _claim: - response: Bytes[32] = raw_call( - token, - _abi_encode( - receiver, - total_claimable, - method_id=method_id("transfer(address,uint256)") - ), - max_outsize=32, - ) - if len(response) != 0: - assert convert(response, bool) + assert ERC20(token).transfer(receiver, total_claimable, default_return_value=True) self.claim_data[_user][token] = total_claimed + total_claimable elif new_claimable > 0: self.claim_data[_user][token] = total_claimed + (total_claimable << 128) @@ -664,38 +660,46 @@ def kick(addr: address): # Administrative Functions +@external +def set_gauge_manager(_gauge_manager: address): + """ + @notice Change the gauge manager for a gauge + @dev The manager of this contract, or the ownership admin can outright modify gauge + managership. A gauge manager can also transfer managership to a new manager via this + method, but only for the gauge which they are the manager of. + @param _gauge_manager The account to set as the new manager of the gauge. + """ + assert msg.sender in [self.manager, Factory(self.factory).admin()] # dev: only manager or factory admin + + self.manager = _gauge_manager + log SetGaugeManager(_gauge_manager) + + @external @nonreentrant("lock") -def deposit_reward_token(_reward_token: address, _amount: uint256): +def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint256 = WEEK): """ @notice Deposit a reward token for distribution @param _reward_token The reward token being deposited @param _amount The amount of `_reward_token` being deposited + @param _epoch The duration the rewards are distributed across. """ assert msg.sender == self.reward_data[_reward_token].distributor self._checkpoint_rewards(empty(address), self.totalSupply, False, empty(address)) - response: Bytes[32] = raw_call( - _reward_token, - _abi_encode( - msg.sender, self, _amount, method_id=method_id("transferFrom(address,address,uint256)") - ), - max_outsize=32, - ) - if len(response) != 0: - assert convert(response, bool) + assert ERC20(_reward_token).transferFrom(msg.sender, self, _amount, default_return_value=True) period_finish: uint256 = self.reward_data[_reward_token].period_finish if block.timestamp >= period_finish: - self.reward_data[_reward_token].rate = _amount / WEEK + self.reward_data[_reward_token].rate = _amount / _epoch else: remaining: uint256 = period_finish - block.timestamp leftover: uint256 = remaining * self.reward_data[_reward_token].rate - self.reward_data[_reward_token].rate = (_amount + leftover) / WEEK + self.reward_data[_reward_token].rate = (_amount + leftover) / _epoch self.reward_data[_reward_token].last_update = block.timestamp - self.reward_data[_reward_token].period_finish = block.timestamp + WEEK + self.reward_data[_reward_token].period_finish = block.timestamp + _epoch @external @@ -705,7 +709,7 @@ def add_reward(_reward_token: address, _distributor: address): @param _reward_token The token to add as an additional reward @param _distributor Address permitted to fund this contract with the reward token """ - assert msg.sender == Factory(self.factory).admin() # dev: only owner + assert msg.sender in [self.manager, Factory(self.factory).admin()] # dev: only manager or factory admin reward_count: uint256 = self.reward_count assert reward_count < MAX_REWARDS diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py new file mode 100644 index 00000000..3135aa90 --- /dev/null +++ b/tests/pools/test_donation_get_D.py @@ -0,0 +1,34 @@ +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + + +# @pytest.mark.skip_rebasing_tokens +@pytest.mark.only_for_pool_type(0) +def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): + + # check if pool is empty: + for i in range(swap.N_COINS()): + assert swap.balances(i) == 0 + + # adding liquidity should not bork: + amounts = [10**17] * swap.N_COINS() + + for token in pool_tokens: + token.approve(swap, 2**256 - 1, sender=bob) + mint_for_testing(bob, 10**18, token, False) + + # check if pool is empty (after minting tokenss): + for i in range(swap.N_COINS()): + assert swap.balances(i) == 0 + + # donate 1 wei and attempt adding liquidity: + pool_tokens[0].transfer(swap, 1, sender=bob) + + if 2 in swap._immutables.asset_types: + with boa.reverts(): + # division by zero error expected for rebasing implementation + swap.add_liquidity(amounts, 0, sender=bob) + else: + swap.add_liquidity(amounts, 0, sender=bob) From b2161c0e4088d8a384195511dff4f95e2995f3f7 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Thu, 21 Sep 2023 21:18:53 +0200 Subject: [PATCH 167/337] fix donation test --- tests/pools/test_donation_get_D.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index 3135aa90..b2ee0249 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -24,7 +24,7 @@ def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): assert swap.balances(i) == 0 # donate 1 wei and attempt adding liquidity: - pool_tokens[0].transfer(swap, 1, sender=bob) + pool_tokens[0].transfer(swap, 10, sender=bob) if 2 in swap._immutables.asset_types: with boa.reverts(): From e6699d1d3a5344037d8a1de3ee828475633626f4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:07:46 +0200 Subject: [PATCH 168/337] fix: add admin slashing immunity comment to docstring --- contracts/main/CurveStableSwapNG.vy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 7c89c3f4..8c2c2eec 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -423,7 +423,11 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: def _balances() -> DynArray[uint256, MAX_COINS]: """ @notice Calculates the pool's balances _excluding_ the admin's balances. - @dev This method ensures LPs keep all rebases and admin only claims swap fees. + @dev If the pool contains rebasing tokens, this method ensures LPs keep all + rebases and admin only claims swap fees. This also means that, since + admin's balances are stored in an array and not inferred from read balances, + the fees in the rebasing token that the admin collects is immune to + slashing events. """ result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances_i: uint256 = 0 From f9460375dbf97c3e7e33e03f40473ac273db3070 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sat, 23 Sep 2023 00:50:35 +0200 Subject: [PATCH 169/337] fix tests --- tests/pools/test_exchange.py | 59 +++++++++++++++++++++++++++++++++--- tests/pools/test_fees.py | 14 +++++---- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index 6df329e0..f0599708 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -6,26 +6,57 @@ class TestExchange: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_min_dy(self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals): + def test_min_dy( + self, + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_token_types, + metapool_token_type, + sending, + receiving, + decimals, + ): amount = 1000 * 10 ** decimals[sending] initial_receiving = ( pool_tokens[receiving].balanceOf(bob) if pool_type == 0 else underlying_tokens[receiving].balanceOf(bob) ) min_dy = swap.get_dy(sending, receiving, amount) + # apply rebasing for expected dy + # Down rebasing breaks dy + if pool_type == 0 and pool_token_types[sending] == 2 and sending == 1: + min_dy -= pool_tokens[sending].balanceOf(swap.address) // 1000000 + swap.exchange(sending, receiving, amount, min_dy - 1, sender=bob) if pool_type == 0: received = pool_tokens[receiving].balanceOf(bob) else: received = underlying_tokens[receiving].balanceOf(bob) - assert abs(received - min_dy - initial_receiving) <= 1 + + if (pool_type == 0 and 2 in pool_token_types) or (pool_type == 1 and metapool_token_type == 2): + assert abs(received - min_dy - initial_receiving) == pytest.approx(1, abs=received // 1000000) + else: + assert abs(received - min_dy - initial_receiving) <= 1 @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_min_dy_imbalanced( - self, bob, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, decimals + self, + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_token_types, + metapool_token_type, + sending, + receiving, + decimals, ): - amounts = [1_000_000 * 10**i for i in decimals] + amounts = [1_500_000 * 10**i for i in decimals] scaler = amounts.copy() # used to scale token amounts when decimals are different amounts[sending] = 0 @@ -33,16 +64,34 @@ def test_min_dy_imbalanced( swap.add_liquidity(amounts, 0, sender=bob) + # oracle + rate = 1 + if pool_type == 0: + if pool_token_types[sending] == 1: + rate = rate / (pool_tokens[sending].exchangeRate() / 10**18) + if pool_token_types[receiving] == 1: + rate = rate * (pool_tokens[receiving].exchangeRate() / 10**18) + + elif pool_type == 1: + if metapool_token_type == 1: + if sending == 0: + rate = rate / (underlying_tokens[0].exchangeRate() / 10**18) + + if receiving == 0: + rate = rate * (underlying_tokens[0].exchangeRate() / 10**18) + # we need to scale these appropriately for tokens with different decimal values min_dy_sending = swap.get_dy(sending, receiving, scaler[sending]) / scaler[receiving] min_dy_receiving = swap.get_dy(receiving, sending, scaler[receiving]) / scaler[sending] - assert min_dy_sending > min_dy_receiving + assert min_dy_sending * rate > min_dy_receiving class TestExchangeReverts: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_insufficient_balance(self, charlie, swap, sending, receiving, decimals): amount = 10 ** decimals[sending] + + # Charlie doesn't have any tokens, all balances are 0 with boa.reverts(): swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) diff --git a/tests/pools/test_fees.py b/tests/pools/test_fees.py index c2c9aca4..b6576715 100644 --- a/tests/pools/test_fees.py +++ b/tests/pools/test_fees.py @@ -57,12 +57,14 @@ def test_withdraw_one_coin( pool_tokens[receiving].balanceOf(swap) if pool_type == 0 else underlying_tokens[receiving].balanceOf(swap) ) assert swap.balances(receiving) == swap_balance - assert ( - admin_balance == pool_tokens[receiving].balanceOf(fee_receiver) + expected_balance = ( + pool_tokens[receiving].balanceOf(fee_receiver) if pool_type == 0 else underlying_tokens[receiving].balanceOf(fee_receiver) ) + assert admin_balance == pytest.approx(expected_balance, abs=1) # +- 1 + def test_no_fees(self, bob, fee_receiver, swap, pool_type, pool_tokens, underlying_tokens): swap.withdraw_admin_fees(sender=bob) @@ -73,8 +75,8 @@ def test_no_fees(self, bob, fee_receiver, swap, pool_type, pool_tokens, underlyi for coin in underlying_tokens: assert coin.balanceOf(fee_receiver) == 0 - def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying_tokens, fee_receiver): - swap.exchange(1, 0, 10**18, 0, sender=bob) + def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying_tokens, fee_receiver, decimals): + swap.exchange(1, 0, 10_000 * 10 ** decimals[1], 0, sender=bob) fees = [] if pool_type == 0: @@ -89,7 +91,7 @@ def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying swap.withdraw_admin_fees(sender=bob) if pool_type == 0: for i, coin in enumerate(pool_tokens): - assert coin.balanceOf(fee_receiver) == fees[i] + assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) else: for i, coin in enumerate(underlying_tokens[:2]): - assert coin.balanceOf(fee_receiver) == fees[i] + assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) From 10db9859e3ad9658551d4efe161e7027005af80c Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:15:40 +0200 Subject: [PATCH 170/337] fix tests --- tests/pools/test_virtual_price.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py index ce5ba050..a239a2ca 100644 --- a/tests/pools/test_virtual_price.py +++ b/tests/pools/test_virtual_price.py @@ -50,7 +50,7 @@ def test_remove(alice, swap, pool_size, deposit_amounts): def test_exchange(bob, swap, sending, receiving, decimals): virtual_price = swap.get_virtual_price() - amount = 10 ** decimals[sending] + amount = 10_000 * 10 ** decimals[sending] swap.exchange( sending, receiving, From dcf88c756e5622b29df0176a22efe156faa51b33 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:58:33 +0200 Subject: [PATCH 171/337] fix: remove unneeded dynarray forming code --- contracts/main/CurveStableSwapNG.vy | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 8c2c2eec..7509d45f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1657,17 +1657,7 @@ def calc_token_amount( @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - amounts[i] = _amounts[i] - - views: address = factory.views_implementation() - return StableSwapViews(views).calc_token_amount(amounts, _is_deposit, self) + return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self) @view From be652549a05ce42b9195d61d3eeb48a2a2f01876 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:09:03 +0200 Subject: [PATCH 172/337] fix remove liquidity test --- tests/pools/test_liquidity.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index 0c6de890..f8793ffa 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -389,9 +389,13 @@ def test_lp_token_balance(self, alice, swap, idx, divisor): initial_amount = swap.balanceOf(alice) amount = initial_amount // divisor - swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + if divisor == 1: + with boa.reverts(): + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + else: + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) - assert swap.balanceOf(alice) + amount == initial_amount + assert swap.balanceOf(alice) + amount == initial_amount @pytest.mark.parametrize("idx", range(2)) def test_expected_vs_actual(self, alice, swap, pool_type, pool_tokens, underlying_tokens, idx): From 2d5f374595eec28885dca6a970b34ce8a8df4e46 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 14:39:16 +0200 Subject: [PATCH 173/337] fix: audit comments #2 --- contracts/main/CurveStableSwapFactoryNG.vy | 8 +++++--- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/LiquidityGauge.vy | 17 +++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 3e7fcf67..ae338d29 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -39,6 +39,7 @@ interface CurvePool: def balances(i: uint256) -> uint256: view def admin_balances(i: uint256) -> uint256: view def get_virtual_price() -> uint256: view + def coins(i: uint256) -> address: view def exchange( i: int128, j: int128, @@ -718,7 +719,6 @@ def deploy_gauge(_pool: address) -> address: def add_base_pool( _base_pool: address, _base_lp_token: address, - _coins: DynArray[address, MAX_COINS], _asset_types: DynArray[uint8, MAX_COINS], _n_coins: uint256, ): @@ -743,11 +743,13 @@ def add_base_pool( self.base_pool_data[_base_pool].asset_types = _asset_types decimals: uint256 = 0 - coins: DynArray[address, MAX_COINS] = _coins + coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + coin: address = empty(address) for i in range(MAX_COINS): if i == _n_coins: break - coin: address = coins[i] + coin = CurvePool(_base_pool).coins(i) + assert coin != 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE # dev: native token is not supported self.base_pool_data[_base_pool].coins.append(coin) self.base_pool_data[_base_pool].asset_types.append(_asset_types[i]) self.base_pool_assets[coin] = True diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 421ab502..929511d4 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -287,7 +287,7 @@ def calc_token_amount( xs = old_balances[i] + new_balance _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) - new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR for idx in range(MAX_COINS): if idx == N_COINS: diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index ed3dfdb0..26fea373 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -240,8 +240,9 @@ def _checkpoint(addr: address): new_rate: uint256 = rate if prev_future_epoch >= _period_time: + future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write() + self.inflation_params = (future_epoch_time_write << 216) + new_rate new_rate = CRV20(CRV).rate() - self.inflation_params = (CRV20(CRV).future_epoch_time_write() << 216) + new_rate if self.is_killed: # Stop distributing inflation as soon as killed @@ -300,6 +301,9 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r """ @notice Claim pending rewards and checkpoint rewards for a user """ + if _total_supply == 0: + return + user_balance: uint256 = 0 receiver: address = _receiver if _user != empty(address): @@ -320,11 +324,10 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r integral: uint256 = self.reward_data[token].integral last_update: uint256 = min(block.timestamp, self.reward_data[token].period_finish) duration: uint256 = last_update - self.reward_data[token].last_update - if duration != 0: + if duration != 0: # to prevent same-block updates self.reward_data[token].last_update = last_update - if _total_supply != 0: - integral += duration * self.reward_data[token].rate * 10**18 / _total_supply - self.reward_data[token].integral = integral + integral += duration * self.reward_data[token].rate * 10**18 / _total_supply + self.reward_data[token].integral = integral if _user != empty(address): integral_for: uint256 = self.reward_integral_for[token][_user] @@ -685,6 +688,7 @@ def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint2 @param _epoch The duration the rewards are distributed across. """ assert msg.sender == self.reward_data[_reward_token].distributor + assert _amount > _epoch # dev: epoch > _amount self._checkpoint_rewards(empty(address), self.totalSupply, False, empty(address)) @@ -710,6 +714,7 @@ def add_reward(_reward_token: address, _distributor: address): @param _distributor Address permitted to fund this contract with the reward token """ assert msg.sender in [self.manager, Factory(self.factory).admin()] # dev: only manager or factory admin + assert _distributor != empty(address) # dev: distributor cannot be zero address reward_count: uint256 = self.reward_count assert reward_count < MAX_REWARDS @@ -729,7 +734,7 @@ def set_reward_distributor(_reward_token: address, _distributor: address): """ current_distributor: address = self.reward_data[_reward_token].distributor - assert msg.sender == current_distributor or msg.sender == Factory(self.factory).admin() + assert msg.sender in [current_distributor, Factory(self.factory).admin(), self.manager] assert current_distributor != empty(address) assert _distributor != empty(address) From 8315d253fd2f5a2ceaab74314f22bac307f180d7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:19:21 +0200 Subject: [PATCH 174/337] fix: do not multiple vprice if we need lp tokens for views method --- contracts/main/CurveStableSwapNGViews.vy | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 929511d4..23c03b55 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -114,7 +114,7 @@ def get_dx_underlying( if i == 0: # Calculate LP tokens that are burnt to receive dy amount of base_j tokens. lp_amount_burnt: uint256 = self._base_calc_token_amounts( - dy, j - 1, meta_v_price, BASE_N_COINS, BASE_POOL, False + dy, j - 1, BASE_N_COINS, BASE_POOL, False ) return self._get_dx(0, 1, lp_amount_burnt, pool, False, N_COINS) @@ -182,8 +182,8 @@ def get_dy_underlying( # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() x = self._base_calc_token_amounts( - dx, base_i, rates[1], base_n_coins, BASE_POOL, True - ) + dx, base_i, base_n_coins, BASE_POOL, True + ) * rates[1] / PRECISION # Accounting for deposit/withdraw fees approximately x -= x * StableSwapNG(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) @@ -455,7 +455,6 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin def _base_calc_token_amounts( dx: uint256, base_i: int128, - meta_vprice: uint256, base_n_coins: uint256, base_pool: address, is_deposit: bool @@ -465,13 +464,13 @@ def _base_calc_token_amounts( base_inputs: uint256[2] = empty(uint256[2]) base_inputs[base_i] = dx - return StableSwap2(base_pool).calc_token_amount(base_inputs, is_deposit) * meta_vprice / PRECISION + return StableSwap2(base_pool).calc_token_amount(base_inputs, is_deposit) elif base_n_coins == 3: base_inputs: uint256[3] = empty(uint256[3]) base_inputs[base_i] = dx - return StableSwap3(base_pool).calc_token_amount(base_inputs, is_deposit) * meta_vprice / PRECISION + return StableSwap3(base_pool).calc_token_amount(base_inputs, is_deposit) else: From daff90b39f4b6d915d841c901c3404ef024c3ce6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:39:42 +0200 Subject: [PATCH 175/337] fix: tests --- tests/fixtures/factory.py | 1 - tests/pools/meta/test_meta.py | 1 - tests/pools/meta/test_meta_new_base.py | 1 - tests/pools/test_exchange.py | 19 +++++++++++++++++-- tests/test_factory.py | 2 -- tests/test_factory_forked.py | 2 +- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 6e552e66..99e683bb 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -96,7 +96,6 @@ def add_base_pool( factory.add_base_pool( base_pool.address, base_pool_lp_token.address, - [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), ) diff --git a/tests/pools/meta/test_meta.py b/tests/pools/meta/test_meta.py index db08d109..8042b024 100644 --- a/tests/pools/meta/test_meta.py +++ b/tests/pools/meta/test_meta.py @@ -88,7 +88,6 @@ def add_base_pool( factory.add_base_pool( base_pool.address, base_pool.address, - [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), ) diff --git a/tests/pools/meta/test_meta_new_base.py b/tests/pools/meta/test_meta_new_base.py index 4c5f506b..944ec63e 100644 --- a/tests/pools/meta/test_meta_new_base.py +++ b/tests/pools/meta/test_meta_new_base.py @@ -87,7 +87,6 @@ def add_base_pool( factory.add_base_pool( base_pool.address, base_pool.address, - [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), ) diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index f0599708..90c55839 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -88,12 +88,27 @@ def test_min_dy_imbalanced( class TestExchangeReverts: @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_insufficient_balance(self, charlie, swap, sending, receiving, decimals): + def test_insufficient_balance( + self, charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals + ): + amount = 10 ** decimals[sending] + for token in pool_tokens + underlying_tokens: + assert token.balanceOf(charlie) == 0 + # Charlie doesn't have any tokens, all balances are 0 - with boa.reverts(): + try: swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) + assert False + except: # noqa: E722 + assert True + + @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) + @pytest.mark.contains_rebasing_tokens + def test_zero_amount_swap(self, charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals): + with boa.reverts(): + swap.exchange(sending, receiving, 0, 0, sender=charlie) @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_min_dy_too_high(self, bob, swap, sending, receiving, decimals): diff --git a/tests/test_factory.py b/tests/test_factory.py index 4a80fc05..e0f3cacc 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -172,7 +172,6 @@ def test_add_base_pool_already_exists( factory.add_base_pool( base_pool.address, base_pool_lp_token.address, - [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), sender=owner, @@ -190,7 +189,6 @@ def test_add_base_pool_only_admin( factory.add_base_pool( base_pool.address, base_pool_lp_token.address, - [t.address for t in base_pool_tokens], [0] * len(base_pool_tokens), len(base_pool_tokens), sender=bob, diff --git a/tests/test_factory_forked.py b/tests/test_factory_forked.py index ea947730..4a3dcc16 100644 --- a/tests/test_factory_forked.py +++ b/tests/test_factory_forked.py @@ -25,6 +25,6 @@ def test_add_base_pool(self, empty_factory, owner, forked_chain): ] assert empty_factory.base_pool_count() == 0 - empty_factory.add_base_pool(susd_pool, lp_token, coins, [0] * len(coins), len(coins), sender=owner) + empty_factory.add_base_pool(susd_pool, lp_token, [0] * len(coins), len(coins), sender=owner) assert empty_factory.base_pool_count() == 1 assert empty_factory.base_pool_list(0) == susd_pool From 496901ed855ef52204a0e4b9e5533fc733138c95 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:01:51 +0200 Subject: [PATCH 176/337] fix: disallow 0 transferFrom into the pool --- contracts/main/CurveStableSwapMetaNG.vy | 1 + contracts/main/CurveStableSwapNG.vy | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 902b3dc7..2672e4de 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -406,6 +406,7 @@ def _transfer_in( else: + assert dx > 0 # dev : do not transferFrom 0 tokens into the pool assert _input_coin.transferFrom( sender, self, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 7509d45f..08704afb 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -342,6 +342,7 @@ def _transfer_in( else: + assert dx > 0 # dev : do not transferFrom 0 tokens into the pool assert ERC20(coins[coin_idx]).transferFrom( sender, self, dx, default_return_value=True ) From 04d2fa445a68ff63ed5bdfe3276e661f06a859da Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 18:18:36 +0200 Subject: [PATCH 177/337] remove unused vars and rename internal fn --- contracts/main/CurveStableSwapNGViews.vy | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 23c03b55..06227bc8 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -103,8 +103,6 @@ def get_dx_underlying( if min(i, j) > 0: raise "Not a Metapool Swap. Use Base pool." - meta_v_price: uint256 = StableSwapNG(pool).stored_rates(1) - # CASE 2: # 1. meta token_0 of (unknown amount) > base pool lp_token # 2. base pool lp_token > calc_withdraw_one_coin gives dy amount of (j-1)th base coin @@ -113,7 +111,7 @@ def get_dx_underlying( # 2. get_dx on metapool for i = 0, and j = 1 (base lp token) with amt calculated in (1). if i == 0: # Calculate LP tokens that are burnt to receive dy amount of base_j tokens. - lp_amount_burnt: uint256 = self._base_calc_token_amounts( + lp_amount_burnt: uint256 = self._base_calc_token_amount( dy, j - 1, BASE_N_COINS, BASE_POOL, False ) return self._get_dx(0, 1, lp_amount_burnt, pool, False, N_COINS) @@ -181,7 +179,7 @@ def get_dy_underlying( # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() - x = self._base_calc_token_amounts( + x = self._base_calc_token_amount( dx, base_i, base_n_coins, BASE_POOL, True ) * rates[1] / PRECISION @@ -452,7 +450,7 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin @internal @view -def _base_calc_token_amounts( +def _base_calc_token_amount( dx: uint256, base_i: int128, base_n_coins: uint256, From 9fa42a11732b9af1dfe6c2b357572b8afc5e2c9e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 18:28:28 +0200 Subject: [PATCH 178/337] check for zero token amt burning --- contracts/main/CurveStableSwapMetaNG.vy | 2 ++ contracts/main/CurveStableSwapNG.vy | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 2672e4de..a18d3f53 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -783,6 +783,7 @@ def remove_liquidity_one_coin( @param _receiver Address that receives the withdrawn coins @return Amount of coin received """ + assert _burn_amount > 0 # dev: do not remove 0 LP tokens dy: uint256 = 0 fee: uint256 = 0 xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -897,6 +898,7 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ + assert _burn_amount > 0 # dev: do not remove 0 LP tokens total_supply: uint256 = self.total_supply amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 08704afb..9bd5ce9d 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -646,6 +646,7 @@ def remove_liquidity_one_coin( @param _receiver Address that receives the withdrawn coins @return Amount of coin received """ + assert _burn_amount > 0 # dev: do not remove 0 LP tokens dy: uint256 = 0 fee: uint256 = 0 xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -762,6 +763,7 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ + assert _burn_amount > 0 # dev: do not remove 0 LP tokens total_supply: uint256 = self.total_supply amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() From d5adba96459cfdd9e114d15d15e9b79425e6cbe2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 27 Sep 2023 19:20:13 +0200 Subject: [PATCH 179/337] fix: use fraxusdc for add_base_pool test since that has coins(u: uint256) abi --- tests/test_factory_forked.py | 42 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/tests/test_factory_forked.py b/tests/test_factory_forked.py index 4a3dcc16..a624525d 100644 --- a/tests/test_factory_forked.py +++ b/tests/test_factory_forked.py @@ -2,29 +2,23 @@ import pytest -class TestFactoryForked: - @pytest.fixture - def empty_factory(self, deployer, fee_receiver, owner): - with boa.env.prank(deployer): - _factory = boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) - return _factory +@pytest.fixture +def empty_factory(deployer, fee_receiver, owner): + with boa.env.prank(deployer): + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + owner, + ) + return _factory - @pytest.mark.only_for_pool_type(1) - def test_add_base_pool(self, empty_factory, owner, forked_chain): - susd_pool = "0xA5407eAE9Ba41422680e2e00537571bcC53efBfD" - lp_token = "0xC25a3A3b969415c80451098fa907EC722572917F" - coins = [ - "0x6B175474E89094C44Da98b954EedeAC495271d0F", - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "0xdAC17F958D2ee523a2206206994597C13D831ec7", - "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", - ] - assert empty_factory.base_pool_count() == 0 - empty_factory.add_base_pool(susd_pool, lp_token, [0] * len(coins), len(coins), sender=owner) - assert empty_factory.base_pool_count() == 1 - assert empty_factory.base_pool_list(0) == susd_pool +@pytest.mark.only_for_pool_type(1) +def test_add_base_pool(empty_factory, owner, forked_chain): + fraxusdc = "0xdcef968d416a41cdac0ed8702fac8128a64241a2" + lp_token = "0x3175df0976dfa876431c2e9ee6bc45b65d3473cc" + + assert empty_factory.base_pool_count() == 0 + empty_factory.add_base_pool(fraxusdc, lp_token, [0, 0], 2, sender=owner) + assert empty_factory.base_pool_count() == 1 + assert empty_factory.base_pool_list(0).lower() == fraxusdc.lower() From b3565b25d71e0ab2bb4ff4b16c582359ae54e9ae Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:00:25 +0200 Subject: [PATCH 180/337] do not disable _checkpoint_rewards entirely --- contracts/main/LiquidityGauge.vy | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 26fea373..30899eae 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -241,8 +241,8 @@ def _checkpoint(addr: address): if prev_future_epoch >= _period_time: future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write() - self.inflation_params = (future_epoch_time_write << 216) + new_rate new_rate = CRV20(CRV).rate() + self.inflation_params = (future_epoch_time_write << 216) + new_rate if self.is_killed: # Stop distributing inflation as soon as killed @@ -301,8 +301,6 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r """ @notice Claim pending rewards and checkpoint rewards for a user """ - if _total_supply == 0: - return user_balance: uint256 = 0 receiver: address = _receiver @@ -324,7 +322,8 @@ def _checkpoint_rewards(_user: address, _total_supply: uint256, _claim: bool, _r integral: uint256 = self.reward_data[token].integral last_update: uint256 = min(block.timestamp, self.reward_data[token].period_finish) duration: uint256 = last_update - self.reward_data[token].last_update - if duration != 0: # to prevent same-block updates + + if duration != 0 and _total_supply != 0: self.reward_data[token].last_update = last_update integral += duration * self.reward_data[token].rate * 10**18 / _total_supply self.reward_data[token].integral = integral From f9f7e0f0947ca482c276732c2b0dc4160f5fd6f7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:04:54 +0200 Subject: [PATCH 181/337] fix: remove double fees accounting in views contract; remove division by zero due to pricision issues in views method; update inflation params if is_killed is set to True --- contracts/main/CurveStableSwapNGViews.vy | 6 ++---- contracts/main/LiquidityGauge.vy | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 06227bc8..433a4949 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -183,10 +183,8 @@ def get_dy_underlying( dx, base_i, base_n_coins, BASE_POOL, True ) * rates[1] / PRECISION - # Accounting for deposit/withdraw fees approximately - x -= x * StableSwapNG(BASE_POOL).fee() / (2 * FEE_DENOMINATOR) # Adding number of pool tokens - x += xp[MAX_COIN] + x += xp[1] else: # If both are from the base pool @@ -207,7 +205,7 @@ def get_dy_underlying( # If output is going via the metapool if j == 0: - dy /= (rates[0] / 10**18) + dy = dy * 10**18 / rates[0] else: # j is from BasePool # The fee is already accounted for diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 30899eae..61979ae2 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -238,13 +238,15 @@ def _checkpoint(addr: address): rate: uint256 = inflation_params % 2 ** 216 prev_future_epoch: uint256 = inflation_params >> 216 new_rate: uint256 = rate + gauge_is_killed: bool = self.is_killed if prev_future_epoch >= _period_time: future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write() new_rate = CRV20(CRV).rate() - self.inflation_params = (future_epoch_time_write << 216) + new_rate + if not gauge_is_killed: + self.inflation_params = (future_epoch_time_write << 216) + new_rate - if self.is_killed: + if gauge_is_killed: # Stop distributing inflation as soon as killed rate = 0 new_rate = 0 # prevent distribution when crossing epochs From 9fad8da3b73f3b4486702558e2a5156b71fad26d Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:05:22 +0200 Subject: [PATCH 182/337] update inflation params if is_killed is set to True --- contracts/main/LiquidityGauge.vy | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 61979ae2..853177e2 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -235,21 +235,20 @@ def _checkpoint(addr: address): _integrate_inv_supply: uint256 = self.integrate_inv_supply[_period] inflation_params: uint256 = self.inflation_params - rate: uint256 = inflation_params % 2 ** 216 prev_future_epoch: uint256 = inflation_params >> 216 - new_rate: uint256 = rate gauge_is_killed: bool = self.is_killed + rate: uint256 = inflation_params % 2 ** 216 + new_rate: uint256 = rate + if gauge_is_killed: + rate = 0 + new_rate = 0 + if prev_future_epoch >= _period_time: future_epoch_time_write: uint256 = CRV20(CRV).future_epoch_time_write() - new_rate = CRV20(CRV).rate() if not gauge_is_killed: - self.inflation_params = (future_epoch_time_write << 216) + new_rate - - if gauge_is_killed: - # Stop distributing inflation as soon as killed - rate = 0 - new_rate = 0 # prevent distribution when crossing epochs + new_rate = CRV20(CRV).rate() + self.inflation_params = (future_epoch_time_write << 216) + new_rate # Update integral of 1/supply if block.timestamp > _period_time: From df4aae3e1d0eb7965c3e4f8116828397386adb29 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:31:55 +0200 Subject: [PATCH 183/337] fix: expose rebasing impl boolean --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index a18d3f53..944dd168 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -174,7 +174,7 @@ N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -POOL_IS_REBASING_IMPLEMENTATION: immutable(bool) +POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 9bd5ce9d..e329c8b0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -135,7 +135,7 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -POOL_IS_REBASING_IMPLEMENTATION: immutable(bool) +POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) From d9f1b2cc52461fc7e12f1be81f8567bf3deaf976 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:49:23 +0200 Subject: [PATCH 184/337] make stored_rates return dynarray; use that in views; fix tests --- contracts/main/CurveStableSwapMetaNG.vy | 4 ++-- contracts/main/CurveStableSwapNG.vy | 4 ++-- contracts/main/CurveStableSwapNGViews.vy | 17 ++++------------- tests/test_oracles.py | 7 ++++--- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 944dd168..7242aa5c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1703,8 +1703,8 @@ def oracle(_idx: uint256) -> address: @view @external -def stored_rates(i: uint256) -> uint256: - return self._stored_rates()[i] +def stored_rates() -> DynArray[uint256, MAX_COINS]: + return self._stored_rates() @view diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index e329c8b0..366e0f60 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1701,8 +1701,8 @@ def oracle(_idx: uint256) -> address: @view @external -def stored_rates(i: uint256) -> uint256: - return self._stored_rates()[i] +def stored_rates() -> DynArray[uint256, MAX_COINS]: + return self._stored_rates() @view diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 433a4949..6c5daee5 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -11,8 +11,9 @@ interface StableSwapNG: def N_COINS() -> uint256: view def BASE_POOL() -> address: view def BASE_N_COINS() -> uint256: view - def stored_rates(i: uint256) -> uint256: view + def stored_rates() -> DynArray[uint256, MAX_COINS]: view def balances(i: uint256) -> uint256: view + def get_balances() -> DynArray[uint256, MAX_COINS]: view def fee() -> uint256: view def get_dy(i: int128, j: int128, dx: uint256) -> uint256: view def A() -> uint256: view @@ -654,18 +655,8 @@ def _get_rates_balances_xp(pool: address, N_COINS: uint256) -> ( DynArray[uint256, MAX_COINS], ): - rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - rate: uint256 = 0 - - # Get rates and balances: - for idx in range(MAX_COINS): - if idx == N_COINS: - break - rate = StableSwapNG(pool).stored_rates(idx) - rates.append(StableSwapNG(pool).stored_rates(idx)) - balances.append(StableSwapNG(pool).balances(idx)) - + rates: DynArray[uint256, MAX_COINS] = StableSwapNG(pool).stored_rates() + balances: DynArray[uint256, MAX_COINS] = StableSwapNG(pool).get_balances() xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for idx in range(MAX_COINS): if idx == N_COINS: diff --git a/tests/test_oracles.py b/tests/test_oracles.py index 366c9474..f5b1412e 100644 --- a/tests/test_oracles.py +++ b/tests/test_oracles.py @@ -15,7 +15,7 @@ def get_D(swap, math): - _rates = [swap.stored_rates(i) for i in range(swap.N_COINS())] + _rates = swap.stored_rates() _balances = swap.internal._balances() xp = swap.internal._xp_mem(_rates, _balances) amp = swap.internal._A() @@ -46,15 +46,16 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): # numeric prices: p_numeric = [] + stored_rates = swap.stored_rates() for n in range(1, swap.N_COINS()): expected_jth_out = views_implementation.get_dy(0, n, 10**18, swap) - p_numeric.append(swap.stored_rates(0) / expected_jth_out) + p_numeric.append(stored_rates[0] / expected_jth_out) # amm prices: p_amm = [] for n in range(swap.N_COINS() - 1): - p_amm.append(swap.get_p(n) * swap.stored_rates(n + 1) / 10**36) + p_amm.append(swap.get_p(n) * stored_rates(n + 1) / 10**36) # compare for n in range(swap.N_COINS() - 1): From 7b408a561ebd7885d3eb043734ca0931cbef8ebf Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:08:05 +0200 Subject: [PATCH 185/337] fix: reduce bytecode and remove asset_type --- contracts/main/CurveStableSwapMetaNG.vy | 2 -- contracts/main/CurveStableSwapNG.vy | 4 ++-- tests/conftest.py | 19 ++++++++++--------- tests/pools/oracle/test_oracle.py | 5 ----- tests/pools/test_donation_get_D.py | 3 +-- tests/pools/test_exchange_received.py | 4 ++-- tests/test_oracles.py | 2 +- 7 files changed, 16 insertions(+), 23 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 7242aa5c..32cfa30b 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -184,7 +184,6 @@ math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] -asset_types: immutable(DynArray[uint8, MAX_COINS]) # Fee specific vars FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -305,7 +304,6 @@ def __init__( BASE_N_COINS = len(_base_coins) coins = _coins # <---------------- coins[1] is always base pool LP token. rate_multipliers = _rate_multipliers - asset_types = _asset_types # contains asset types for all pool tokens including base pool tokens POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 366e0f60..14b3c94d 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -140,7 +140,6 @@ POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] -asset_types: immutable(DynArray[uint8, MAX_COINS]) # Fee specific vars FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -258,7 +257,6 @@ def __init__( self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) rate_multipliers = _rate_multipliers - asset_types = _asset_types POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types factory = Factory(msg.sender) @@ -788,6 +786,8 @@ def remove_liquidity( if _claim_admin_fees: self._withdraw_admin_fees() + # Upkeep D_oracle + return amounts diff --git a/tests/conftest.py b/tests/conftest.py index 1787acfc..d32f8402 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -169,10 +169,10 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals): # @pytest.mark.only_for_token_types(2) # class TestPoolsWithOracleToken: @pytest.fixture(autouse=True) -def skip_by_token_type(request, swap): +def skip_by_token_type(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: - asset_types = swap._immutables.asset_types + asset_types = [tkn.asset_types() for tkn in pool_tokens] if not any(asset_type in only_for_token_types.args for asset_type in asset_types): pytest.skip("skipped because no tokens for these types") @@ -181,25 +181,26 @@ def skip_by_token_type(request, swap): def skip_rebasing(request, swap): only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") if only_for_token_types: - asset_types_contains_rebasing = 2 in swap._immutables.asset_types - if asset_types_contains_rebasing: + if swap.POOL_IS_REBASING_IMPLEMENTATION(): pytest.skip("skipped because test includes rebasing tokens") @pytest.fixture(autouse=True) -def skip_oracle(request, swap): +def skip_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") if only_for_token_types: - asset_types_contains_oracle = 1 in swap._immutables.asset_types + asset_types = [tkn.asset_types() for tkn in pool_tokens] + asset_types_contains_oracle = 1 in asset_types if asset_types_contains_oracle: pytest.skip("skipped because test includes oraclised tokens") @pytest.fixture(autouse=True) -def only_oracle(request, swap): +def only_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") if only_for_token_types: - asset_types_contains_rebasing = 1 in swap._immutables.asset_types + asset_types = [tkn.asset_types() for tkn in pool_tokens] + asset_types_contains_rebasing = 1 in asset_types if not asset_types_contains_rebasing: pytest.skip("skipped because test excludes oraclised tokens") @@ -208,7 +209,7 @@ def only_oracle(request, swap): def only_rebasing(request, swap): marker = request.node.get_closest_marker("contains_rebasing_tokens") if marker: - asset_types_contains_rebasing = 2 in swap._immutables.asset_types + asset_types_contains_rebasing = swap.POOL_IS_REBASING_IMPLEMENTATION() if not asset_types_contains_rebasing: pytest.skip("skipped because test excludes rebasing tokens") diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index 00f8e459..b6257905 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -85,11 +85,6 @@ def test_initial_liquidity( def test_oracles(self, alice, swap, pool_size, pool_type, pool_token_types, metapool_token_type): assert swap._storage.oracles.get() != [0] * pool_size - if pool_type == 0: - assert swap._immutables.asset_types == pool_token_types - else: - assert swap._immutables.asset_types == [metapool_token_type, 0, 0, 0, 0] - def test_get_dy( self, alice, diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index b2ee0249..d770a2b7 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -25,8 +25,7 @@ def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): # donate 1 wei and attempt adding liquidity: pool_tokens[0].transfer(swap, 10, sender=bob) - - if 2 in swap._immutables.asset_types: + if swap.POOL_IS_REBASING_IMPLEMENTATION(): with boa.reverts(): # division by zero error expected for rebasing implementation swap.add_liquidity(amounts, 0, sender=bob) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 76369467..d32801f8 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -207,7 +207,7 @@ def test_exchange_underlying_not_received(bob, swap, sending, receiving): @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): - if 2 in swap._immutables.asset_types: + if swap.POOL_IS_REBASING_IMPLEMENTATION(): with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) @@ -216,6 +216,6 @@ def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - if 2 in swap._immutables.asset_types: + if swap.POOL_IS_REBASING_IMPLEMENTATION(): with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/test_oracles.py b/tests/test_oracles.py index f5b1412e..89e26849 100644 --- a/tests/test_oracles.py +++ b/tests/test_oracles.py @@ -55,7 +55,7 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): # amm prices: p_amm = [] for n in range(swap.N_COINS() - 1): - p_amm.append(swap.get_p(n) * stored_rates(n + 1) / 10**36) + p_amm.append(swap.get_p(n) * stored_rates[n + 1] / 10**36) # compare for n in range(swap.N_COINS() - 1): From 145b3901b018e315f572955abe0ac4e3ee7d2a10 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:30:01 +0200 Subject: [PATCH 186/337] remove unneeded rate oracle view method to save bytecode --- contracts/main/CurveStableSwapMetaNG.vy | 6 ------ contracts/main/CurveStableSwapNG.vy | 6 ------ 2 files changed, 12 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 32cfa30b..3675a414 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1693,12 +1693,6 @@ def get_balances() -> DynArray[uint256, MAX_COINS]: return self._balances() -@view -@external -def oracle(_idx: uint256) -> address: - return convert(self.oracles[0] % 2**160, address) - - @view @external def stored_rates() -> DynArray[uint256, MAX_COINS]: diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 14b3c94d..257a12dd 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1693,12 +1693,6 @@ def get_balances() -> DynArray[uint256, MAX_COINS]: return self._balances() -@view -@external -def oracle(_idx: uint256) -> address: - return convert(self.oracles[_idx] % 2**160, address) - - @view @external def stored_rates() -> DynArray[uint256, MAX_COINS]: From f61003f7579b2bc02ab1e3d51bf854d2c3d6c5f1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:17:55 +0200 Subject: [PATCH 187/337] chainsec comment: upkeep D oracle on remove_liquidity --- contracts/main/CurveStableSwapMetaNG.vy | 10 +-- contracts/main/CurveStableSwapNG.vy | 101 ++++++++++++++++++------ 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 3675a414..e88fa659 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -313,7 +313,7 @@ def __init__( # _exchange_underlying: ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) + self.last_prices_packed.append(self.pack_2(10**18, 10**18)) # ----------------- Parameters independent of pool type ------------------ @@ -750,7 +750,7 @@ def add_liquidity( mint_amount = D1 # Take the dust if there was any # (re)instantiate D oracle if totalSupply is zero. - self.last_D_packed = self.pack_prices(D1, D1) + self.last_D_packed = self.pack_2(D1, D1) assert mint_amount >= _min_mint_amount, "Slippage screwed you" @@ -1284,7 +1284,7 @@ def _calc_withdraw_one_coin( @pure @internal -def pack_prices(p1: uint256, p2: uint256) -> uint256: +def pack_2(p1: uint256, p2: uint256) -> uint256: assert p1 < 2**128 assert p2 < 2**128 return p1 | (p2 << 128) @@ -1332,7 +1332,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): if spot_price[0] != 0: # Upate packed prices ----------------- - last_prices_packed_new[0] = self.pack_prices( + last_prices_packed_new[0] = self.pack_2( spot_price[0], self._calc_moving_average( last_prices_packed_current[0], @@ -1345,7 +1345,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # ---------------------------- Upkeep D oracle --------------------------- last_D_packed_current: uint256 = self.last_D_packed - self.last_D_packed = self.pack_prices( + self.last_D_packed = self.pack_2( D, self._calc_moving_average(last_D_packed_current, self.D_ma_time) ) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 257a12dd..a4021347 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -174,7 +174,9 @@ last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_pri last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) D_ma_time: public(uint256) -ma_last_time: public(uint256) +ma_last_time: public(uint256) # packing: ma_last_time_p, ma_last_time_D +# ma_last_time has a distinction for p and D because p is _not_ updated if +# users remove_liquidity, but D is. # shift(2**32 - 1, 224) ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 @@ -254,7 +256,7 @@ def __init__( for i in range(MAX_COINS): if i == __n_coins - 1: break - self.last_prices_packed.append(self.pack_prices(10**18, 10**18)) + self.last_prices_packed.append(self.pack_2(10**18, 10**18)) rate_multipliers = _rate_multipliers POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types @@ -270,7 +272,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time self.D_ma_time = 62324 # <--------- 12 hours default on contract start. - self.ma_last_time = block.timestamp + self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) for i in range(MAX_COINS_128): @@ -613,7 +615,7 @@ def add_liquidity( mint_amount = D1 # Take the dust if there was any # (re)instantiate D oracle if totalSupply is zero. - self.last_D_packed = self.pack_prices(D1, D1) + self.last_D_packed = self.pack_2(D1, D1) assert mint_amount >= _min_mint_amount, "Slippage screwed you" @@ -761,8 +763,9 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ - assert _burn_amount > 0 # dev: do not remove 0 LP tokens total_supply: uint256 = self.total_supply + assert _burn_amount > 0 and _burn_amount < total_supply # dev: invalid _burn_amount + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -772,22 +775,46 @@ def remove_liquidity( if i == N_COINS_128: break - value = balances[i] * _burn_amount / total_supply + value = unsafe_div(balances[i] * _burn_amount, total_supply) assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts.append(value) self._transfer_out(i, value, _receiver) - total_supply -= _burn_amount - self._burnFrom(msg.sender, _burn_amount) + self._burnFrom(msg.sender, _burn_amount) # <---- Updates self.total_supply + + # --------------------------- Upkeep D_oracle ---------------------------- + + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + last_D_packed_current: uint256 = self.last_D_packed + old_D: uint256 = last_D_packed_current & (2**128 - 1) + + self.last_D_packed = self.pack_2( + old_D - unsafe_div(old_D * _burn_amount, total_supply), # new_D = proportionally reduce D. + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1] + ) + ) + + if ma_last_time_unpacked[1] < block.timestamp: + ma_last_time_unpacked[1] = block.timestamp - log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + + # ------------------------------- Log event ------------------------------ + + log RemoveLiquidity( + msg.sender, + amounts, + empty(DynArray[uint256, MAX_COINS]), + total_supply - _burn_amount + ) - # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. + # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- if _claim_admin_fees: self._withdraw_admin_fees() - # Upkeep D_oracle - return amounts @@ -1213,12 +1240,18 @@ def _calc_withdraw_one_coin( @pure @internal -def pack_prices(p1: uint256, p2: uint256) -> uint256: +def pack_2(p1: uint256, p2: uint256) -> uint256: assert p1 < 2**128 assert p2 < 2**128 return p1 | (p2 << 128) +@pure +@internal +def unpack_2(packed: uint256) -> uint256[2]: + return [packed & (2**128 - 1), packed >> 128] + + @internal @pure def _get_p( @@ -1256,7 +1289,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): """ @notice Upkeeps price and D oracles. """ - ma_last_time: uint256 = self.ma_last_time + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current @@ -1272,11 +1305,12 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): if spot_price[i] != 0: # Upate packed prices ----------------- - last_prices_packed_new[i] = self.pack_prices( + last_prices_packed_new[i] = self.pack_2( spot_price[i], self._calc_moving_average( last_prices_packed_current[i], - self.ma_exp_time + self.ma_exp_time, + ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices ) ) @@ -1285,20 +1319,30 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # ---------------------------- Upkeep D oracle --------------------------- last_D_packed_current: uint256 = self.last_D_packed - self.last_D_packed = self.pack_prices( + self.last_D_packed = self.pack_2( D, - self._calc_moving_average(last_D_packed_current, self.D_ma_time) + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1], # index 0 is ma_exp_time for D + ) ) # Housekeeping: Update ma_last_time ------------------ - if ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + for i in range(2): + if ma_last_time_unpacked[i] < block.timestamp: + ma_last_time_unpacked[i] = block.timestamp + + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) @internal @view -def _calc_moving_average(packed_value: uint256, averaging_window: uint256) -> uint256: - ma_last_time: uint256 = self.ma_last_time +def _calc_moving_average( + packed_value: uint256, + averaging_window: uint256, + ma_last_time: uint256 +) -> uint256: last_spot_value: uint256 = packed_value & (2**128 - 1) last_ema_value: uint256 = (packed_value >> 128) @@ -1347,14 +1391,22 @@ def get_p(i: uint256) -> uint256: @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - return self._calc_moving_average(self.last_prices_packed[i], self.ma_exp_time) + return self._calc_moving_average( + self.last_prices_packed[i], + self.ma_exp_time, + self.ma_last_time + ) @external @view @nonreentrant('lock') def D_oracle() -> uint256: - return self._calc_moving_average(self.last_D_packed, self.D_ma_time) + return self._calc_moving_average( + self.last_D_packed, + self.D_ma_time, + self.ma_last_time + ) # ----------------------------- Math Utils ----------------------------------- @@ -1363,7 +1415,6 @@ def D_oracle() -> uint256: @internal @pure def exp(x: int256) -> uint256: - """ @dev Calculates the natural exponential function of a signed integer with a precision of 1e18. From 72699ee6b331e26c7df7990dc6e3465c7292bafb Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:20:40 +0200 Subject: [PATCH 188/337] fix: use correct last times in view methods --- contracts/main/CurveStableSwapNG.vy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index a4021347..2e699ec0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1328,7 +1328,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): ) ) - # Housekeeping: Update ma_last_time ------------------ + # Housekeeping: Update ma_last_time for p and D oracles ------------------ for i in range(2): if ma_last_time_unpacked[i] < block.timestamp: ma_last_time_unpacked[i] = block.timestamp @@ -1394,7 +1394,7 @@ def price_oracle(i: uint256) -> uint256: return self._calc_moving_average( self.last_prices_packed[i], self.ma_exp_time, - self.ma_last_time + self.ma_last_time & (2**128 - 1) ) @@ -1405,7 +1405,7 @@ def D_oracle() -> uint256: return self._calc_moving_average( self.last_D_packed, self.D_ma_time, - self.ma_last_time + self.ma_last_time >> 128 ) From 6e6461059af149dc42ecc973e5fe4aa8a55f3f7f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:40:57 +0200 Subject: [PATCH 189/337] fix: comment --- contracts/main/CurveStableSwapNG.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 2e699ec0..c5cfe1f7 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1324,7 +1324,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): self._calc_moving_average( last_D_packed_current, self.D_ma_time, - ma_last_time_unpacked[1], # index 0 is ma_exp_time for D + ma_last_time_unpacked[1], # index 1 is ma_exp_time for D ) ) From 50c0d655813339bc3385d5be977f7bce89a3253f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:51:08 +0200 Subject: [PATCH 190/337] add D oracle upkeeping in remove_liquidity for metapools --- contracts/main/CurveStableSwapMetaNG.vy | 85 ++++++++++++++++++++----- contracts/main/CurveStableSwapNG.vy | 2 +- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e88fa659..a635902c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -218,7 +218,7 @@ last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_pri last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) D_ma_time: public(uint256) -ma_last_time: public(uint256) +ma_last_time: public(uint256) # packing: ma_last_time_p, ma_last_time_D # shift(2**32 - 1, 224) ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 @@ -328,7 +328,7 @@ def __init__( assert _ma_exp_time != 0 self.ma_exp_time = _ma_exp_time self.D_ma_time = 62324 # <--------- 12 hours default on contract start. - self.ma_last_time = block.timestamp + self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) for i in range(N_COINS_128): @@ -896,8 +896,8 @@ def remove_liquidity( @param _receiver Address that receives the withdrawn coins @return List of amounts of coins that were withdrawn """ - assert _burn_amount > 0 # dev: do not remove 0 LP tokens total_supply: uint256 = self.total_supply + assert _burn_amount > 0 and _burn_amount <= total_supply # dev: invalid _burn_amount amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -905,16 +905,44 @@ def remove_liquidity( for i in range(N_COINS_128): - value = balances[i] * _burn_amount / total_supply + value = unsafe_div(balances[i] * _burn_amount, total_supply) assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts.append(value) self._transfer_out(i, value, _receiver) self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds - log RemoveLiquidity(msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), total_supply) + # --------------------------- Upkeep D_oracle ---------------------------- + + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + last_D_packed_current: uint256 = self.last_D_packed + old_D: uint256 = last_D_packed_current & (2**128 - 1) + + self.last_D_packed = self.pack_2( + old_D - unsafe_div(old_D * _burn_amount, total_supply), # new_D = proportionally reduce D. + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1] + ) + ) + + if ma_last_time_unpacked[1] < block.timestamp: + ma_last_time_unpacked[1] = block.timestamp + + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + + # ------------------------------- Log event ------------------------------ + + log RemoveLiquidity( + msg.sender, + amounts, + empty(DynArray[uint256, MAX_COINS]), + total_supply - _burn_amount + ) + + # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- - # Withdraw admin fees if _claim_admin_fees is set to True. Helps automate. if _claim_admin_fees: self._withdraw_admin_fees() @@ -1290,6 +1318,12 @@ def pack_2(p1: uint256, p2: uint256) -> uint256: return p1 | (p2 << 128) +@pure +@internal +def unpack_2(packed: uint256) -> uint256[2]: + return [packed & (2**128 - 1), packed >> 128] + + @internal @pure def _get_p( @@ -1320,7 +1354,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): """ @notice Upkeeps price and D oracles. """ - ma_last_time: uint256 = self.ma_last_time + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current @@ -1336,7 +1370,8 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): spot_price[0], self._calc_moving_average( last_prices_packed_current[0], - self.ma_exp_time + self.ma_exp_time, + ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices ) ) @@ -1347,18 +1382,28 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): last_D_packed_current: uint256 = self.last_D_packed self.last_D_packed = self.pack_2( D, - self._calc_moving_average(last_D_packed_current, self.D_ma_time) + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1], # index 1 is ma_exp_time for D + ) ) - # Housekeeping: Update ma_last_time ------------------ - if ma_last_time < block.timestamp: - self.ma_last_time = block.timestamp + # Housekeeping: Update ma_last_time for p and D oracles ------------------ + for i in range(2): + if ma_last_time_unpacked[i] < block.timestamp: + ma_last_time_unpacked[i] = block.timestamp + + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) @internal @view -def _calc_moving_average(packed_value: uint256, averaging_window: uint256) -> uint256: - ma_last_time: uint256 = self.ma_last_time +def _calc_moving_average( + packed_value: uint256, + averaging_window: uint256, + ma_last_time: uint256 +) -> uint256: last_spot_value: uint256 = packed_value & (2**128 - 1) last_ema_value: uint256 = (packed_value >> 128) @@ -1410,14 +1455,22 @@ def get_p(i: uint256) -> uint256: @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: assert i == 0 # dev: metapools do not have price oracle indices greater than 0. - return self._calc_moving_average(self.last_prices_packed[0], self.ma_exp_time) + return self._calc_moving_average( + self.last_prices_packed[0], + self.ma_exp_time, + self.ma_last_time & (2**128 - 1), + ) @external @view @nonreentrant('lock') def D_oracle() -> uint256: - return self._calc_moving_average(self.last_D_packed, self.D_ma_time) + return self._calc_moving_average( + self.last_D_packed, + self.D_ma_time, + self.ma_last_time >> 128 + ) # ---------------------------- ERC20 Utils ----------------------------------- diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index c5cfe1f7..61d89cf2 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -764,7 +764,7 @@ def remove_liquidity( @return List of amounts of coins that were withdrawn """ total_supply: uint256 = self.total_supply - assert _burn_amount > 0 and _burn_amount < total_supply # dev: invalid _burn_amount + assert _burn_amount > 0 and _burn_amount <= total_supply # dev: invalid _burn_amount amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() From 919f1d007f9b12e45bde6482a6d8755645d1cade Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:40:34 +0200 Subject: [PATCH 191/337] final auditor comments --- contracts/main/CurveStableSwapFactoryNG.vy | 40 ++++------- contracts/main/CurveStableSwapMetaNG.vy | 82 ++++++++++------------ contracts/main/CurveStableSwapNG.vy | 49 ++++++------- contracts/main/CurveStableSwapNGViews.vy | 1 - contracts/main/LiquidityGauge.vy | 28 +++++--- 5 files changed, 87 insertions(+), 113 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index ae338d29..40cb1654 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -30,7 +30,6 @@ interface ERC20: def balanceOf(_addr: address) -> uint256: view def decimals() -> uint256: view def totalSupply() -> uint256: view - def approve(_spender: address, _amount: uint256): nonpayable interface CurvePool: def A() -> uint256: view @@ -40,13 +39,6 @@ interface CurvePool: def admin_balances(i: uint256) -> uint256: view def get_virtual_price() -> uint256: view def coins(i: uint256) -> address: view - def exchange( - i: int128, - j: int128, - dx: uint256, - min_dy: uint256, - _receiver: address, - ) -> uint256: nonpayable interface CurveFactoryMetapool: def coins(i :uint256) -> address: view @@ -74,7 +66,6 @@ event LiquidityGaugeDeployed: gauge: address MAX_COINS: constant(uint256) = 8 -ADDRESS_PROVIDER: constant(address) = 0x0000000022D53366457F9d5E68Ec105046FC4383 MAX_FEE: constant(uint256) = 5 * 10 ** 9 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -86,8 +77,8 @@ pool_list: public(address[4294967296]) # master list of pools pool_count: public(uint256) # actual length of pool_list pool_data: HashMap[address, PoolArray] -base_pool_list: public(address[4294967296]) # master list of pools -base_pool_count: public(uint256) # actual length of pool_list +base_pool_list: public(address[4294967296]) # list of base pools +base_pool_count: public(uint256) # number of base pools base_pool_data: public(HashMap[address, BasePoolArray]) # asset -> is used in a metapool? @@ -442,7 +433,11 @@ def get_pool_asset_types(_pool: address) -> DynArray[uint8, MAX_COINS]: """ @notice Query the asset type of `_pool` @param _pool Pool Address - @return Integer indicating the pool asset type + @return Dynarray of uint8 indicating the pool asset type + Asset Types: + 0. Standard ERC20 token with no additional features + 1. Oracle - token with rate oracle (e.g. wrapped staked ETH) + 2. Rebasing - token with rebase (e.g. staked ETH) """ return self.pool_data[_pool].asset_types @@ -476,8 +471,7 @@ def deploy_plain_pool( * Non-redeemable, collateralized assets: 100 * Redeemable assets: 200-400 @param _fee Trade fee, given as an integer with 1e10 precision. The - the maximum is 1% (100000000). - 50% of the fee is distributed to veCRV holders. + maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 @param _implementation_idx Index of the implementation to use @@ -488,9 +482,10 @@ def deploy_plain_pool( @param _oracles Array of rate oracle addresses. @return Address of the deployed pool """ - assert len(_coins) == len(_method_ids), "All coin arrays should be same length" - assert len(_coins) == len(_oracles), "All coin arrays should be same length" - assert len(_coins) == len(_asset_types), "All coin arrays should be same length" + assert len(_coins) >= 2 # dev: pool needs to have at least two coins! + assert len(_coins) == len(_method_ids) # dev: All coin arrays should be same length + assert len(_coins) == len(_oracles) # dev: All coin arrays should be same length + assert len(_coins) == len(_asset_types) # dev: All coin arrays should be same length assert _fee <= 100000000, "Invalid fee" assert _offpeg_fee_multiplier * _fee <= MAX_FEE * FEE_DENOMINATOR @@ -548,14 +543,7 @@ def deploy_plain_pool( coin: address = _coins[i] self.pool_data[pool].coins.append(coin) - raw_call( - coin, - concat( - method_id("approve(address,uint256)"), - convert(pool, bytes32), - convert(max_value(uint256), bytes32) - ) - ) + for j in range(i, i + MAX_COINS): if (j + 1) == n_coins: break @@ -658,8 +646,6 @@ def deploy_metapool( code_offset=3 ) - ERC20(_coin).approve(pool, max_value(uint256)) - # add pool to pool_list length: uint256 = self.pool_count self.pool_list[length] = pool diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index a635902c..4bd343a3 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -51,9 +51,9 @@ interface ERC1271: interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view def get_dx_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy_underlying(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy_underlying(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view def calc_token_amount( _amounts: DynArray[uint256, MAX_COINS], @@ -297,6 +297,7 @@ def __init__( Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. """ + assert len(_base_coins) <= 3 # dev: implementation does not support base pool with more than 3 coins math = Math(_math_implementation) BASE_POOL = _base_pool @@ -375,7 +376,8 @@ def _transfer_in( ) -> uint256: """ @notice Contains all logic to handle ERC20 token transfers. - @param _coin address of the coin to transfer in. + @param coin_metapool_idx metapool index of input coin + @param coin_basepool_idx basepool index of input coin @param dx amount of `_coin` to transfer into the pool. @param sender address to transfer `_coin` from. @param expect_optimistic_transfer True if contract expects an optimistic coin transfer @@ -438,12 +440,12 @@ def _transfer_out( ): """ @notice Transfer a single token from the pool to receiver. - @param _coin Address of the token to transfer out + @param _coin_idx Index of the token to transfer out @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ - # 'gulp' coin balance of the pool to stored_balances here to account for + # Read coin balance of the pool to stored_balances here to account for # donations etc. coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) @@ -470,31 +472,23 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: this method queries that rate by static-calling an external contract. """ - rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - if BASE_POOL != empty(address): - rates = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] - else: - rates = rate_multipliers + rates: DynArray[uint256, MAX_COINS] = [ + rate_multipliers[0], + StableSwap(BASE_POOL).get_virtual_price() + ] oracles: DynArray[uint256, MAX_COINS] = self.oracles + if not self.oracles[0] == 0: - for i in range(N_COINS_128): - - if oracles[i] == 0: - continue - - # NOTE: assumed that response is of precision 10**18 response: Bytes[32] = raw_call( - convert(oracles[i] % 2**160, address), - _abi_encode(oracles[i] & ORACLE_BIT_MASK), + convert(oracles[0] % 2**160, address), + _abi_encode(oracles[0] & ORACLE_BIT_MASK), max_outsize=32, is_static_call=True, ) - assert len(response) != 0 - - # rates[i] * convert(response, uint256) / PRECISION - rates[i] = unsafe_div(rates[i] * convert(response, uint256), PRECISION) + # rates[0] * convert(response, uint256) / PRECISION + rates[0] = unsafe_div(rates[0] * convert(response, uint256), PRECISION) return rates @@ -541,7 +535,7 @@ def exchange( @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to recieve @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -741,9 +735,9 @@ def add_liquidity( new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = math.get_D(xp, amp, N_COINS) - mint_amount = total_supply * (D2 - D0) / D0 - self.upkeep_oracles(xp, amp, D2) + D1 = math.get_D(xp, amp, N_COINS) # <------ Reuse D1 for new D value. + mint_amount = total_supply * (D1 - D0) / D0 + self.upkeep_oracles(xp, amp, D1) else: @@ -864,12 +858,12 @@ def remove_liquidity_imbalance( new_balances[i] -= fees[i] - D2: uint256 = self.get_D_mem(rates, new_balances, amp) + D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. - self.upkeep_oracles(new_balances, amp, D2) + self.upkeep_oracles(new_balances, amp, D1) total_supply: uint256 = self.total_supply - burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 + burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -897,7 +891,7 @@ def remove_liquidity( @return List of amounts of coins that were withdrawn """ total_supply: uint256 = self.total_supply - assert _burn_amount > 0 and _burn_amount <= total_supply # dev: invalid _burn_amount + assert _burn_amount > 0 # dev: invalid _burn_amount amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -977,7 +971,6 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: @internal def __exchange( - dx: uint256, x: uint256, _xp: DynArray[uint256, MAX_COINS], rates: DynArray[uint256, MAX_COINS], @@ -1042,7 +1035,7 @@ def _exchange( # xp[i] + dx * rates[i] / PRECISION x: uint256 = xp[i] + unsafe_div(dx * rates[i], PRECISION) - dy: uint256 = self.__exchange(dx, x, xp, rates, i, j) + dy: uint256 = self.__exchange(x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" # --------------------------- Do Transfer out ---------------------------- @@ -1067,8 +1060,6 @@ def _exchange_underlying( expect_optimistic_transfer: bool = False ) -> uint256: - assert BASE_POOL != empty(address) # dev: pool is not a metapool - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() old_balances: DynArray[uint256, MAX_COINS] = self._balances() xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) @@ -1079,21 +1070,20 @@ def _exchange_underlying( meta_i: int128 = 0 meta_j: int128 = 0 x: uint256 = 0 - input_coin: address = empty(address) output_coin: address = empty(address) # ------------------------ Determine coin indices ------------------------ - if i == 0: - input_coin = coins[0] - else: - base_i = i - MAX_METAPOOL_COIN_INDEX # if i == 1, this reverts + # Get input coin indices: + if i > 0: + base_i = i - MAX_METAPOOL_COIN_INDEX meta_i = 1 - input_coin = BASE_COINS[base_i] + + # Get output coin and indices: if j == 0: output_coin = coins[0] else: - base_j = j - MAX_METAPOOL_COIN_INDEX # if j == 1, this reverts + base_j = j - MAX_METAPOOL_COIN_INDEX meta_j = 1 output_coin = BASE_COINS[base_j] @@ -1128,7 +1118,7 @@ def _exchange_underlying( x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) x += xp[MAX_METAPOOL_COIN_INDEX] - dy = self.__exchange(dx_w_fee, x, xp, rates, meta_i, meta_j) + dy = self.__exchange(x, xp, rates, meta_i, meta_j) # Adjust stored balances of meta-level tokens: self.stored_balances[meta_j] -= dy @@ -1289,10 +1279,10 @@ def _calc_withdraw_one_coin( xp_j = xp[j] if j == i: dx_expected = xp_j * D1 / D0 - new_y - xavg = (xp[j] + new_y) / 2 + xavg = (xp_j + new_y) / 2 else: dx_expected = xp_j - xp_j * D1 / D0 - xavg = xp[j] + xavg = xp_j # xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) @@ -1690,7 +1680,9 @@ def totalSupply() -> uint256: def get_virtual_price() -> uint256: """ @notice The current virtual price of the pool LP token - @dev Useful for calculating profits + @dev Useful for calculating profits. + The method may be vulnerable to donation-style attacks if implementation + contains rebasing tokens. For integrators, caution is advised. @return LP token virtual price normalized to 1e18 """ xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 61d89cf2..5bcab258 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -50,7 +50,7 @@ interface ERC1271: interface StableSwapViews: def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view def calc_token_amount( _amounts: DynArray[uint256, MAX_COINS], @@ -317,14 +317,12 @@ def __init__( def _transfer_in( coin_idx: int128, dx: uint256, - dy: uint256, sender: address, - receiver: address, expect_optimistic_transfer: bool, ) -> uint256: """ @notice Contains all logic to handle ERC20 token transfers. - @param _coin address of the coin to transfer in. + @param coin_idx Index of the coin to transfer in. @param dx amount of `_coin` to transfer into the pool. @param dy amount of `_coin` to transfer out of the pool. @param sender address to transfer `_coin` from. @@ -361,8 +359,8 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): """ @notice Transfer a single token from the pool to receiver. @dev This function is called by `remove_liquidity` and - `remove_liquidity_one` and `_exchange` methods. - @param _coin Address of the token to transfer out + `remove_liquidity_one`, `_exchange` and `_withdraw_admin_fees` methods. + @param _coin_idx Index of the token to transfer out @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ @@ -412,8 +410,6 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: max_outsize=32, is_static_call=True, ) - - assert len(response) != 0 rates[i] = rates[i] * convert(response, uint256) / PRECISION return rates @@ -464,7 +460,7 @@ def exchange( @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to recieve @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -550,9 +546,7 @@ def add_liquidity( new_balances[i] += self._transfer_in( i, _amounts[i], - 0, msg.sender, - empty(address), False, # expect_optimistic_transfer ) @@ -606,9 +600,9 @@ def add_liquidity( new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D2: uint256 = self.get_D(xp, amp) - mint_amount = total_supply * (D2 - D0) / D0 - self.upkeep_oracles(xp, amp, D2) + D1 = self.get_D(xp, amp) # <--------------- Reuse D1 for new D value. + mint_amount = total_supply * (D1 - D0) / D0 + self.upkeep_oracles(xp, amp, D1) else: @@ -730,12 +724,12 @@ def remove_liquidity_imbalance( self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR new_balances[i] -= fees[i] - D2: uint256 = self.get_D_mem(rates, new_balances, amp) + D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. - self.upkeep_oracles(new_balances, amp, D2) + self.upkeep_oracles(new_balances, amp, D1) total_supply: uint256 = self.total_supply - burn_amount: uint256 = ((D0 - D2) * total_supply / D0) + 1 + burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -764,7 +758,7 @@ def remove_liquidity( @return List of amounts of coins that were withdrawn """ total_supply: uint256 = self.total_supply - assert _burn_amount > 0 and _burn_amount <= total_supply # dev: invalid _burn_amount + assert _burn_amount > 0 # dev: invalid burn amount amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -775,7 +769,7 @@ def remove_liquidity( if i == N_COINS_128: break - value = unsafe_div(balances[i] * _burn_amount, total_supply) + value = balances[i] * _burn_amount / total_supply assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts.append(value) self._transfer_out(i, value, _receiver) @@ -846,7 +840,6 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: @internal def __exchange( - dx: uint256, x: uint256, _xp: DynArray[uint256, MAX_COINS], rates: DynArray[uint256, MAX_COINS], @@ -902,16 +895,14 @@ def _exchange( dx: uint256 = self._transfer_in( i, _dx, - _min_dy, sender, - receiver, expect_optimistic_transfer ) # ------------------------------- Exchange ------------------------------- x: uint256 = xp[i] + dx * rates[i] / PRECISION - dy: uint256 = self.__exchange(dx, x, xp, rates, i, j) + dy: uint256 = self.__exchange(x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" # --------------------------- Do Transfer out ---------------------------- @@ -978,9 +969,7 @@ def get_y( amp: uint256 = _amp D: uint256 = _D - if _D == 0: - amp = self._A() - D = self.get_D(xp, amp) + S_: uint256 = 0 _x: uint256 = 0 y_prev: uint256 = 0 @@ -1218,10 +1207,10 @@ def _calc_withdraw_one_coin( if j == i: dx_expected = xp_j * D1 / D0 - new_y - xavg = (xp[j] + new_y) / 2 + xavg = (xp_j + new_y) / 2 else: dx_expected = xp_j - xp_j * D1 / D0 - xavg = xp[j] + xavg = xp_j dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR @@ -1686,7 +1675,9 @@ def totalSupply() -> uint256: def get_virtual_price() -> uint256: """ @notice The current virtual price of the pool LP token - @dev Useful for calculating profits + @dev Useful for calculating profits. + The method may be vulnerable to donation-style attacks if implementation + contains rebasing tokens. For integrators, caution is advised. @return LP token virtual price normalized to 1e18 """ amp: uint256 = self._A() diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 6c5daee5..3a82484e 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -350,7 +350,6 @@ def calc_withdraw_one_coin(_burn_amount: uint256, i: int128, pool: address) -> u xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1, N_COINS) - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors return dy diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 853177e2..af441f5b 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -89,7 +89,6 @@ MAX_REWARDS: constant(uint256) = 8 TOKENLESS_PRODUCTION: constant(uint256) = 40 WEEK: constant(uint256) = 604800 -# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 VERSION: constant(String[8]) = "v6.0.0" # <- updated from v5.0.0 (adds `create_from_blueprint` pattern) EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") @@ -171,8 +170,6 @@ def __init__(_lp_token: address): @notice Contract constructor @param _lp_token Liquidity Pool contract address """ - assert self.lp_token == empty(address) - self.lp_token = _lp_token self.factory = msg.sender self.manager = msg.sender @@ -259,7 +256,7 @@ def _checkpoint(addr: address): for i in range(500): dt: uint256 = week_time - prev_week_time - w: uint256 = Controller(GAUGE_CONTROLLER).gauge_relative_weight(self, prev_week_time / WEEK * WEEK) + w: uint256 = Controller(GAUGE_CONTROLLER).gauge_relative_weight(self, prev_week_time) if _working_supply > 0: if prev_future_epoch >= prev_week_time and prev_future_epoch < week_time: @@ -413,7 +410,7 @@ def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = @param _value Number of tokens to deposit @param _addr Address to deposit for """ - + assert _addr != empty(address) # dev: cannot deposit for zero address self._checkpoint(_addr) if _value != 0: @@ -431,8 +428,8 @@ def deposit(_value: uint256, _addr: address = msg.sender, _claim_rewards: bool = ERC20(self.lp_token).transferFrom(msg.sender, self, _value) - log Deposit(_addr, _value) - log Transfer(empty(address), _addr, _value) + log Deposit(_addr, _value) + log Transfer(empty(address), _addr, _value) @external @@ -688,19 +685,28 @@ def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint2 @param _epoch The duration the rewards are distributed across. """ assert msg.sender == self.reward_data[_reward_token].distributor - assert _amount > _epoch # dev: epoch > _amount self._checkpoint_rewards(empty(address), self.totalSupply, False, empty(address)) - assert ERC20(_reward_token).transferFrom(msg.sender, self, _amount, default_return_value=True) + # transferFrom reward token and use transferred amount henceforth: + amount_received: uint256 = ERC20(_reward_token).balanceOf(self) + assert ERC20(_reward_token).transferFrom( + msg.sender, + self, + _amount, + default_return_value=True + ) + amount_received = ERC20(_reward_token).balanceOf(self) - amount_received period_finish: uint256 = self.reward_data[_reward_token].period_finish + assert amount_received > _epoch # dev: rate will tend to zero! + if block.timestamp >= period_finish: - self.reward_data[_reward_token].rate = _amount / _epoch + self.reward_data[_reward_token].rate = amount_received / _epoch # TODO: consider using precision here hmm else: remaining: uint256 = period_finish - block.timestamp leftover: uint256 = remaining * self.reward_data[_reward_token].rate - self.reward_data[_reward_token].rate = (_amount + leftover) / _epoch + self.reward_data[_reward_token].rate = (amount_received + leftover) / _epoch self.reward_data[_reward_token].last_update = block.timestamp self.reward_data[_reward_token].period_finish = block.timestamp + _epoch From 72aabcf24452076a80889c4977e7ff427a27a393 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:38:13 +0200 Subject: [PATCH 192/337] update to vypers 0.3.10rc6. final candidate: 0.3.10; all hail king charles --- contracts/main/CurveStableSwapFactoryNG.vy | 2 +- .../main/CurveStableSwapFactoryNGHandler.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 5 +- contracts/main/CurveStableSwapNG.vy | 4 +- contracts/main/CurveStableSwapNGMath.vy | 2 +- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/LiquidityGauge.vy | 3 +- contracts/mocks/CallbackSwap.vy | 3 +- contracts/mocks/CurvePool.vy | 2 +- contracts/mocks/CurveTokenV3.vy | 2 +- poetry.lock | 76 ++++++++++++++++--- pyproject.toml | 4 +- tests/fixtures/factory.py | 6 +- 13 files changed, 86 insertions(+), 27 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 40cb1654..ff70f0bc 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version 0.3.10rc6 """ @title CurveStableswapFactoryNG @author Curve.Fi diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index 1c86e019..cdf7c40a 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version 0.3.10rc6 """ @title CurveStableswapFactoryNGHandler @author Curve.Fi diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 4bd343a3..f1b386db 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,4 +1,5 @@ -# @version 0.3.9 +# @version 0.3.10rc6 +#pragma optimize codesize """ @title CurveStableSwapMetaNG @author Curve.Fi @@ -567,6 +568,7 @@ def exchange_received( this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens directly to the contract and call `exchange_received`. + Note: This is disabled if pool contains rebasing tokens. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged @@ -625,6 +627,7 @@ def exchange_underlying_received( ) -> uint256: """ @notice Perform an exchange between two underlying coins + @dev This is disabled if pool contains rebasing tokens. @param i Index value for the underlying coin to send @param j Index value of the underlying coin to receive @param _dx Amount of `i` being exchanged diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 5bcab258..17861f4f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,4 +1,5 @@ -# @version 0.3.9 +# @version 0.3.10rc6 +#pragma optimize gas """ @title CurveStableSwapNG @author Curve.Fi @@ -492,6 +493,7 @@ def exchange_received( this method are dex aggregators, arbitrageurs, or other users who do not wish to grant approvals to the contract: they would instead send tokens directly to the contract and call `exchange_received`. + Note: This is disabled if pool contains rebasing tokens. @param i Index value for the coin to send @param j Index valie of the coin to recieve @param _dx Amount of `i` being exchanged diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 09d1c6aa..a8125ce9 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version 0.3.10rc6 """ @title CurveStableSwapNGMath @author Curve.Fi diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 3a82484e..94db215d 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version 0.3.10rc6 """ @title CurveStableSwap2NG @author Curve.Fi diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index af441f5b..3466616c 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -1,5 +1,4 @@ -# @version 0.3.9 - +# @version 0.3.10rc6 """ @title LiquidityGaugeV6 @author Curve.Fi diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index 81102d1c..d19c21b4 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -1,5 +1,4 @@ -# @version 0.3.9 - +# @version ^0.3.9 """ @title CurveExchangeWithoutApproval @author fiddyresearch.eth diff --git a/contracts/mocks/CurvePool.vy b/contracts/mocks/CurvePool.vy index 2f32ff0a..22a7bde1 100644 --- a/contracts/mocks/CurvePool.vy +++ b/contracts/mocks/CurvePool.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version ^0.3.9 """ @title StableSwap @author Curve.Fi diff --git a/contracts/mocks/CurveTokenV3.vy b/contracts/mocks/CurveTokenV3.vy index d9541355..0af3dc1b 100644 --- a/contracts/mocks/CurveTokenV3.vy +++ b/contracts/mocks/CurveTokenV3.vy @@ -1,4 +1,4 @@ -# @version 0.3.9 +# @version ^0.3.9 """ @title Curve LP Token @author Curve.Fi diff --git a/poetry.lock b/poetry.lock index 4e0f7c2e..f062212a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -412,6 +412,55 @@ files = [ {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] +[[package]] +name = "cbor2" +version = "5.4.6" +description = "CBOR (de)serializer with extensive tag support" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cbor2-5.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:309fffbb7f561d67f02095d4b9657b73c9220558701c997e9bfcfbca2696e927"}, + {file = "cbor2-5.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff95b33e5482313a74648ca3620c9328e9f30ecfa034df040b828e476597d352"}, + {file = "cbor2-5.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9eb582fce972f0fa429d8159b7891ff8deccb7affc4995090afc61ce0d328a"}, + {file = "cbor2-5.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3950be57a1698086cf26d8710b4e5a637b65133c5b1f9eec23967d4089d8cfed"}, + {file = "cbor2-5.4.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:78304df140b9e13b93bcbb2aecee64c9aaa9f1cadbd45f043b5e7b93cc2f21a2"}, + {file = "cbor2-5.4.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e73ca40dd3c7210ff776acff9869ddc9ff67bae7c425b58e5715dcf55275163f"}, + {file = "cbor2-5.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:0b956f19e93ba3180c336282cd1b6665631f2d3a196a9c19b29a833bf979e7a4"}, + {file = "cbor2-5.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c12c0ab78f5bc290b08a79152a8621822415836a86f8f4b50dadba371736fda"}, + {file = "cbor2-5.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3545b16f9f0d5f34d4c99052829c3726020a07be34c99c250d0df87418f02954"}, + {file = "cbor2-5.4.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24144822f8d2b0156f4cda9427f071f969c18683ffed39663dc86bc0a75ae4dd"}, + {file = "cbor2-5.4.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1835536e76ea16e88c934aac5e369ba9f93d495b01e5fa2d93f0b4986b89146d"}, + {file = "cbor2-5.4.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:39452c799453f5bf33281ffc0752c620b8bfa0b7c13070b87d370257a1311976"}, + {file = "cbor2-5.4.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3316f09a77af85e7772ecfdd693b0f450678a60b1aee641bac319289757e3fa0"}, + {file = "cbor2-5.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:456cdff668a50a52fdb8aa6d0742511e43ed46d6a5b463dba80a5a720fa0d320"}, + {file = "cbor2-5.4.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9394ca49ecdf0957924e45d09a4026482d184a465a047f60c4044eb464c43de9"}, + {file = "cbor2-5.4.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dfa030cd3d67e5b6701d3067923f2f61536a8ffb1b45be14775d1e866b59ae"}, + {file = "cbor2-5.4.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5094562dfe3e5583202b93ef7ca5082c2ba5571accb2c4412d27b7d0ba8a563"}, + {file = "cbor2-5.4.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:94f844d0e232aca061a86dd6ff191e47ba0389ddd34acb784ad9a41594dc99a4"}, + {file = "cbor2-5.4.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7bbd3470eb685325398023e335be896b74f61b014896604ed45049a7b7b6d8ac"}, + {file = "cbor2-5.4.6-cp37-cp37m-win_amd64.whl", hash = "sha256:0bd12c54a48949d11f5ffc2fa27f5df1b4754111f5207453e5fae3512ebb3cab"}, + {file = "cbor2-5.4.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2984a488f350aee1d54fa9cb8c6a3c1f1f5b268abbc91161e47185de4d829f3"}, + {file = "cbor2-5.4.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c285a2cb2c04004bfead93df89d92a0cef1874ad337d0cb5ea53c2c31e97bfdb"}, + {file = "cbor2-5.4.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6709d97695205cd08255363b54afa035306d5302b7b5e38308c8ff5a47e60f2a"}, + {file = "cbor2-5.4.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96087fa5336ebfc94465c0768cd5de0fcf9af3840d2cf0ce32f5767855f1a293"}, + {file = "cbor2-5.4.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0d2b926b024d3a1549b819bc82fdc387062bbd977b0299dd5fa5e0ea3267b98b"}, + {file = "cbor2-5.4.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6e1b5aee920b6a2f737aa12e2b54de3826b09f885a7ce402db84216343368140"}, + {file = "cbor2-5.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:79e048e623846d60d735bb350263e8fdd36cb6195d7f1a2b57eacd573d9c0b33"}, + {file = "cbor2-5.4.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80ac8ba450c7a41c5afe5f7e503d3092442ed75393e1de162b0bf0d97edf7c7f"}, + {file = "cbor2-5.4.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ce1a2c272ba8523a55ea2f1d66e3464e89fa0e37c9a3d786a919fe64e68dbd7"}, + {file = "cbor2-5.4.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1618d16e310f7ffed141762b0ff5d8bb6b53ad449406115cc465bf04213cefcf"}, + {file = "cbor2-5.4.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbbdb2e3ef274865dc3f279aae109b5d94f4654aea3c72c479fb37e4a1e7ed7"}, + {file = "cbor2-5.4.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f9c702bee2954fffdfa3de95a5af1a6b1c5f155e39490353d5654d83bb05bb9"}, + {file = "cbor2-5.4.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b9f3924da0e460a93b3674c7e71020dd6c9e9f17400a34e52a88c0af2dcd2aa"}, + {file = "cbor2-5.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:d54bd840b4fe34f097b8665fc0692c7dd175349e53976be6c5de4433b970daa4"}, + {file = "cbor2-5.4.6-py3-none-any.whl", hash = "sha256:181ac494091d1f9c5bb373cd85514ce1eb967a8cf3ec298e8dfa8878aa823956"}, + {file = "cbor2-5.4.6.tar.gz", hash = "sha256:b893500db0fe033e570c3adc956af6eefc57e280026bd2d86fd53da9f1e594d7"}, +] + +[package.extras] +doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["pytest", "pytest-cov"] + [[package]] name = "certifi" version = "2023.5.7" @@ -3823,8 +3872,8 @@ forking-recommended = ["plyvel", "ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "e29a70640b67c3e87c248a582454ae6fe8eeec00" -resolved_reference = "e29a70640b67c3e87c248a582454ae6fe8eeec00" +reference = "40f5bfcc2afe212bb5a6f5026148f3625596ded7" +resolved_reference = "40f5bfcc2afe212bb5a6f5026148f3625596ded7" [[package]] name = "tomli" @@ -3988,28 +4037,33 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "vyper" -version = "0.3.9" +version = "0.3.10rc6" description = "Vyper: the Pythonic Programming Language for the EVM" optional = false python-versions = ">=3.10,<4" -files = [ - {file = "vyper-0.3.9-py3-none-any.whl", hash = "sha256:eed86456068ddbf5329de831fb51c4e2741ac786d75bd86df925d1d0569ce8e9"}, - {file = "vyper-0.3.9.tar.gz", hash = "sha256:e140521f8a91060b32f953bd5f3a2c59e74ceb9475953a3d9a5d82f794fd3800"}, -] +files = [] +develop = false [package.dependencies] asttokens = ">=2.0.5,<3" +cbor2 = ">=5.4.6,<6" importlib-metadata = "*" +packaging = ">=23.1,<24" pycryptodome = ">=3.5.1,<4" -semantic-version = ">=2.10,<3" wheel = "*" [package.extras] -dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.910)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] +dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] -lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.910)"] +lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] +[package.source] +type = "git" +url = "https://github.com/vyperlang/vyper.git" +reference = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39" +resolved_reference = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39" + [[package]] name = "watchdog" version = "3.0.0" @@ -4484,4 +4538,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a217037d6e131fdf20301059bfe64d527a22557e8d3101afb00c106ccd8922fb" +content-hash = "c3d4972e56d89e5038804f0ade9fd9b578f1d3599100fd87c7401bf34b82fcf6" diff --git a/pyproject.toml b/pyproject.toml index 298cda6d..6665d364 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "e29a70640b67c3e87c248a582454ae6fe8eeec00"} -vyper = "^0.3.9" +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "40f5bfcc2afe212bb5a6f5026148f3625596ded7"} +vyper = {git = "https://github.com/vyperlang/vyper.git", rev = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39"} pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 99e683bb..8f4baf72 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -21,7 +21,8 @@ def amm_interface(): @pytest.fixture(scope="module") def amm_implementation(deployer, amm_interface): with boa.env.prank(deployer): - return amm_interface.deploy_as_blueprint() + impl = amm_interface.deploy_as_blueprint() + return impl @pytest.fixture(scope="module") @@ -32,7 +33,8 @@ def amm_interface_meta(): @pytest.fixture(scope="module") def amm_implementation_meta(deployer, amm_interface_meta): with boa.env.prank(deployer): - return amm_interface_meta.deploy_as_blueprint() + impl = amm_interface_meta.deploy_as_blueprint() + return impl @pytest.fixture(scope="module") From 7a285fde3d7b1002ca35d2a3c8bf57a169db3bab Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:47:48 +0200 Subject: [PATCH 193/337] fix: version pragma --- tests/test_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_token.py b/tests/test_token.py index fa67081b..c3283391 100644 --- a/tests/test_token.py +++ b/tests/test_token.py @@ -106,7 +106,7 @@ def test_permit(self, eth_acc, bob, swap): def test_permit_contract(self, eth_acc, bob, swap): # based on https://eips.ethereum.org/EIPS/eip-1271 src = """ - # @version 0.3.9 + # @version ^0.3.9 OWNER: public(immutable(address)) @external From 7b766a338d4292ea5272954468128e1017a2fd12 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:28:57 +0200 Subject: [PATCH 194/337] rename to set because apply is commit->apply, whereas here we're setting it in one tx --- contracts/main/CurveStableSwapMetaNG.vy | 6 +++--- contracts/main/CurveStableSwapNG.vy | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index f1b386db..bd488b7e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1800,15 +1800,15 @@ def stop_ramp_A(): @external -def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): +def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): assert msg.sender == factory.admin() - # apply new fee: + # set new fee: assert _new_fee <= MAX_FEE self.fee = _new_fee - # apply new offpeg_fee_multiplier: + # set new offpeg_fee_multiplier: assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 17861f4f..60b1e1f0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1796,15 +1796,15 @@ def stop_ramp_A(): @external -def apply_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): +def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): assert msg.sender == factory.admin() - # apply new fee: + # set new fee: assert _new_fee <= MAX_FEE self.fee = _new_fee - # apply new offpeg_fee_multiplier: + # set new offpeg_fee_multiplier: assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier From 6d313ee964128a79df059f2b90bb4027bfed4e6f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:02:01 +0200 Subject: [PATCH 195/337] update vyper to 0.3.10 ! --- contracts/main/CurveStableSwapFactoryNG.vy | 2 +- .../main/CurveStableSwapFactoryNGHandler.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 2 +- contracts/main/CurveStableSwapNGMath.vy | 3 ++- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/LiquidityGauge.vy | 3 ++- poetry.lock | 16 ++++++---------- pyproject.toml | 2 +- 9 files changed, 16 insertions(+), 18 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index ff70f0bc..60ff962e 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,4 +1,4 @@ -# @version 0.3.10rc6 +# @version 0.3.10 """ @title CurveStableswapFactoryNG @author Curve.Fi diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index cdf7c40a..648ff5ac 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,4 +1,4 @@ -# @version 0.3.10rc6 +# @version 0.3.10 """ @title CurveStableswapFactoryNGHandler @author Curve.Fi diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index bd488b7e..0a0f63dc 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,4 +1,4 @@ -# @version 0.3.10rc6 +# @version 0.3.10 #pragma optimize codesize """ @title CurveStableSwapMetaNG diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 60b1e1f0..e95b56fa 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,4 +1,4 @@ -# @version 0.3.10rc6 +# @version 0.3.10 #pragma optimize gas """ @title CurveStableSwapNG diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index a8125ce9..78478b7f 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,4 +1,5 @@ -# @version 0.3.10rc6 +# @version 0.3.10 +#pragma optimize gas """ @title CurveStableSwapNGMath @author Curve.Fi diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 94db215d..23fd1aa1 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,4 +1,4 @@ -# @version 0.3.10rc6 +# @version 0.3.10 """ @title CurveStableSwap2NG @author Curve.Fi diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 3466616c..6174a6e7 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -1,4 +1,5 @@ -# @version 0.3.10rc6 +# @version 0.3.10 +#pragma optimize gas """ @title LiquidityGaugeV6 @author Curve.Fi diff --git a/poetry.lock b/poetry.lock index f062212a..1ba13f0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4037,12 +4037,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "vyper" -version = "0.3.10rc6" +version = "0.3.10" description = "Vyper: the Pythonic Programming Language for the EVM" optional = false python-versions = ">=3.10,<4" -files = [] -develop = false +files = [ + {file = "vyper-0.3.10-py3-none-any.whl", hash = "sha256:05636302341bf89602b19f749fcabc8d184a265d8eea4a45c20b3259780353b0"}, + {file = "vyper-0.3.10.tar.gz", hash = "sha256:8dc1f501caab417fb0ce9c68a6944587f0147ec7cc7d3889cf3a45c19466e489"}, +] [package.dependencies] asttokens = ">=2.0.5,<3" @@ -4058,12 +4060,6 @@ docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] -[package.source] -type = "git" -url = "https://github.com/vyperlang/vyper.git" -reference = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39" -resolved_reference = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39" - [[package]] name = "watchdog" version = "3.0.0" @@ -4538,4 +4534,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c3d4972e56d89e5038804f0ade9fd9b578f1d3599100fd87c7401bf34b82fcf6" +content-hash = "8fe9e234f49d624b6796c4e8f6eb71b98972ecf8f4155cc5a2ce231e2b825a90" diff --git a/pyproject.toml b/pyproject.toml index 6665d364..c8eaf46b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ packages = [] python = "^3.10" poetry = "1.5.1" titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "40f5bfcc2afe212bb5a6f5026148f3625596ded7"} -vyper = {git = "https://github.com/vyperlang/vyper.git", rev = "e9c16e40dd11ba21ba817ff0da78eaeab744fd39"} +vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" From bff1522b30819b7b240af17ccfb72b0effbf6c47 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:10:38 +0200 Subject: [PATCH 196/337] bump gauge version --- contracts/main/LiquidityGauge.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 6174a6e7..710baa8f 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -89,7 +89,7 @@ MAX_REWARDS: constant(uint256) = 8 TOKENLESS_PRODUCTION: constant(uint256) = 40 WEEK: constant(uint256) = 604800 -VERSION: constant(String[8]) = "v6.0.0" # <- updated from v5.0.0 (adds `create_from_blueprint` pattern) +VERSION: constant(String[8]) = "v6.1.0" # <- updated from v6.0.0 (makes rewards semi-permissionless) EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") From 237828dcd968d429e39fe8cab65e017f6ebfd0e5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 11 Oct 2023 19:10:16 +0200 Subject: [PATCH 197/337] add erc4626 token support in asset_type = 3 --- contracts/main/CurveStableSwapMetaNG.vy | 42 +++++++++++++++++-------- contracts/main/CurveStableSwapNG.vy | 36 +++++++++++++++------ contracts/main/LiquidityGauge.vy | 2 +- tests/pools/test_swap_getters.py | 2 +- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 0a0f63dc..e92081f2 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -13,6 +13,7 @@ 0. Standard ERC20 token with no additional features 1. Oracle - token with rate oracle (e.g. wstETH) 2. Rebasing - token with rebase (e.g. stETH) + 3. ERC4626 - token with convertToAssets method (e.g. sDAI). Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -37,6 +38,8 @@ """ from vyper.interfaces import ERC20 +from vyper.interfaces import ERC20Detailed +from vyper.interfaces import ERC4626 implements: ERC20 @@ -184,6 +187,7 @@ BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) +asset_types: public(immutable(DynArray[uint8, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -305,6 +309,7 @@ def __init__( BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) coins = _coins # <---------------- coins[1] is always base pool LP token. + asset_types = _asset_types rate_multipliers = _rate_multipliers POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types @@ -446,8 +451,6 @@ def _transfer_out( @param receiver Address to send the tokens to """ - # Read coin balance of the pool to stored_balances here to account for - # donations etc. coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) # ------------------------- Handle Transfers ----------------------------- @@ -477,19 +480,33 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price() ] - oracles: DynArray[uint256, MAX_COINS] = self.oracles - if not self.oracles[0] == 0: - response: Bytes[32] = raw_call( - convert(oracles[0] % 2**160, address), - _abi_encode(oracles[0] & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, + if not self.oracles[0] == 0 and asset_types[0] == 1: + + # NOTE: fetched_rate is assumed to be 10**18 precision + fetched_rate: uint256 = convert( + raw_call( + convert(oracles[0] % 2**160, address), + _abi_encode(oracles[0] & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ), + uint256 ) - # rates[0] * convert(response, uint256) / PRECISION - rates[0] = unsafe_div(rates[0] * convert(response, uint256), PRECISION) + # rates[0] * fetched_rate / PRECISION + rates[0] = unsafe_div(rates[0] * fetched_rate, PRECISION) + + elif asset_types[0] == 3: # ERC4626 + + coin_decimals: uint256 = convert(ERC20Detailed(coins[0]).decimals(), uint256) + + # rates[0] * fetched_rate / PRECISION + rates[0] = unsafe_div( + rates[0] * ERC4626(coins[0]).convertToAssets(10**coin_decimals) * 10**(18 - coin_decimals), + PRECISION + ) return rates @@ -1713,8 +1730,7 @@ def calc_token_amount( @view @external def A() -> uint256: - amp: uint256 = self._A() - return unsafe_div(amp, A_PRECISION) + return unsafe_div(self._A(), A_PRECISION) @view diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index e95b56fa..459a93d2 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -13,6 +13,7 @@ 0. Standard ERC20 token with no additional features 1. Oracle - token with rate oracle (e.g. wstETH) 2. Rebasing - token with rebase (e.g. stETH) + 3. ERC4626 - token with convertToAssets method (e.g. sDAI). Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -36,6 +37,8 @@ """ from vyper.interfaces import ERC20 +from vyper.interfaces import ERC20Detailed +from vyper.interfaces import ERC4626 implements: ERC20 @@ -140,6 +143,7 @@ POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) +asset_types: public(immutable(DynArray[uint8, MAX_COINS])) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -250,6 +254,7 @@ def __init__( """ coins = _coins + asset_types = _asset_types __n_coins: uint256 = len(_coins) N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) @@ -366,8 +371,6 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): @param receiver Address to send the tokens to """ - # 'gulp' coin balance of the pool to stored_balances here to account for - # donations etc. coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) # ------------------------- Handle Transfers ----------------------------- @@ -404,14 +407,27 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: if oracles[i] == 0: continue - # NOTE: assumed that response is of precision 10**18 - response: Bytes[32] = raw_call( - convert(oracles[i] % 2**160, address), - _abi_encode(oracles[i] & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ) - rates[i] = rates[i] * convert(response, uint256) / PRECISION + if asset_types[i] == 1: + + # NOTE: fetched_rate is assumed to be 10**18 precision + fetched_rate: uint256 = convert( + raw_call( + convert(oracles[i] % 2**160, address), + _abi_encode(oracles[i] & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ), + uint256 + ) + + rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) + + elif asset_types[i] == 3: # ERC4626 + + coin_decimals: uint256 = convert(ERC20Detailed(coins[i]).decimals(), uint256) + fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(10**coin_decimals) * 10**(18 - coin_decimals) + + rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) return rates diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 710baa8f..100ce72e 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -702,7 +702,7 @@ def deposit_reward_token(_reward_token: address, _amount: uint256, _epoch: uint2 assert amount_received > _epoch # dev: rate will tend to zero! if block.timestamp >= period_finish: - self.reward_data[_reward_token].rate = amount_received / _epoch # TODO: consider using precision here hmm + self.reward_data[_reward_token].rate = amount_received / _epoch else: remaining: uint256 = period_finish - block.timestamp leftover: uint256 = remaining * self.reward_data[_reward_token].rate diff --git a/tests/pools/test_swap_getters.py b/tests/pools/test_swap_getters.py index 31ee6188..dfccf6e2 100644 --- a/tests/pools/test_swap_getters.py +++ b/tests/pools/test_swap_getters.py @@ -2,7 +2,7 @@ from boa.test import strategy from hypothesis import given, settings -SETTINGS = {"max_examples": 100, "deadline": 1000} +SETTINGS = {"max_examples": 100, "deadline": None} @given( From 63b46dcb15328fe5c9497ad41857f5680609fc6a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:09:25 +0200 Subject: [PATCH 198/337] add asset types; fix: _stored_rates in plain pools not reading external rates --- contracts/main/CurveStableSwapFactoryNG.vy | 20 ++++++++++++++++++++ contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 5 +---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 60ff962e..393a0ffd 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -73,6 +73,8 @@ FEE_DENOMINATOR: constant(uint256) = 10 ** 10 admin: public(address) future_admin: public(address) +asset_types: public(HashMap[uint8, String[20]]) + pool_list: public(address[4294967296]) # master list of pools pool_count: public(uint256) # actual length of pool_list pool_data: HashMap[address, PoolArray] @@ -107,6 +109,12 @@ def __init__(_fee_receiver: address, _owner: address): self.fee_receiver = _fee_receiver self.admin = _owner + self.asset_types[0] = "Standard" + self.asset_types[1] = "Oracle" + self.asset_types[2] = "Rebasing" + self.asset_types[3] = "ERC4626" + + # <--- Factory Getters ---> @@ -438,6 +446,7 @@ def get_pool_asset_types(_pool: address) -> DynArray[uint8, MAX_COINS]: 0. Standard ERC20 token with no additional features 1. Oracle - token with rate oracle (e.g. wrapped staked ETH) 2. Rebasing - token with rebase (e.g. staked ETH) + 3. ERC4626 - e.g. sDAI """ return self.pool_data[_pool].asset_types @@ -840,3 +849,14 @@ def set_fee_receiver(_pool: address, _fee_receiver: address): """ assert msg.sender == self.admin # dev: admin only self.fee_receiver = _fee_receiver + + +@external +def add_asset_type(_id: uint8, _name: String[10]): + """ + @notice Admin only method that adds a new asset type. + @param _id asset type id. + @param _name Name of the asset type. + """ + assert msg.sender == self.admin # dev: admin only + self.asset_types[_id] = _name diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index e92081f2..66115917 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -482,7 +482,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: ] oracles: DynArray[uint256, MAX_COINS] = self.oracles - if not self.oracles[0] == 0 and asset_types[0] == 1: + if asset_types[0] == 1 and not self.oracles[0] == 0: # NOTE: fetched_rate is assumed to be 10**18 precision fetched_rate: uint256 = convert( diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 459a93d2..5304a11c 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -404,10 +404,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: if i == N_COINS_128: break - if oracles[i] == 0: - continue - - if asset_types[i] == 1: + if asset_types[i] == 1 and not oracles[i] == 0: # NOTE: fetched_rate is assumed to be 10**18 precision fetched_rate: uint256 = convert( From fa9731af120f92ec55a059cc6b09f0e26174b81d Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:03:08 +0200 Subject: [PATCH 199/337] use correct asset precisions for erc4626 --- contracts/main/CurveStableSwapMetaNG.vy | 22 +- contracts/main/CurveStableSwapNG.vy | 45 ++- contracts/mocks/ERC4626.vy | 270 ++++++++++++++++++ tests/pools/test_erc4626_swaps.py | 169 +++++++++++ ...est_specific_liquidity_operations copy.py} | 0 5 files changed, 489 insertions(+), 17 deletions(-) create mode 100644 contracts/mocks/ERC4626.vy create mode 100644 tests/pools/test_erc4626_swaps.py rename tests/pools/{test_specific_liquidity_operations.py => test_specific_liquidity_operations copy.py} (100%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 66115917..d13bceee 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -219,6 +219,10 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] +# For ERC4626 tokens, we need: +call_amount: immutable(uint256) +scale_factor: immutable(uint256) + last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) @@ -320,7 +324,14 @@ def __init__( # _exchange_underlying: ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) - self.last_prices_packed.append(self.pack_2(10**18, 10**18)) + # For ERC4626 tokens: + if asset_types[0] == 3: + # In Vyper 0.3.10, if immutables are not set, because of an if-statement, + # it is by default set to 0; this is fine in the case of these two + # immutables, since they are only used if asset_types[0] == 3. + call_amount = 10**convert(ERC20Detailed(_coins[0]).decimals(), uint256) + _underlying_asset: address = ERC4626(_coins[0]).asset() + scale_factor = 10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256)) # ----------------- Parameters independent of pool type ------------------ @@ -337,6 +348,9 @@ def __init__( self.D_ma_time = 62324 # <--------- 12 hours default on contract start. self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) + # ------------------- initialize storage for DynArrays ------------------ + + self.last_prices_packed.append(self.pack_2(10**18, 10**18)) for i in range(N_COINS_128): self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) @@ -500,13 +514,11 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: elif asset_types[0] == 3: # ERC4626 - coin_decimals: uint256 = convert(ERC20Detailed(coins[0]).decimals(), uint256) - # rates[0] * fetched_rate / PRECISION rates[0] = unsafe_div( - rates[0] * ERC4626(coins[0]).convertToAssets(10**coin_decimals) * 10**(18 - coin_decimals), + rates[0] * ERC4626(coins[0]).convertToAssets(call_amount) * scale_factor, PRECISION - ) + ) # 1e18 precision return rates diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 5304a11c..2baa8e6b 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -175,6 +175,10 @@ rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] +# For ERC4626 tokens, we need: +call_amount: immutable(DynArray[uint256, MAX_COINS]) +scale_factor: immutable(DynArray[uint256, MAX_COINS]) + last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) @@ -259,11 +263,6 @@ def __init__( N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) - for i in range(MAX_COINS): - if i == __n_coins - 1: - break - self.last_prices_packed.append(self.pack_2(10**18, 10**18)) - rate_multipliers = _rate_multipliers POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types @@ -280,18 +279,37 @@ def __init__( self.D_ma_time = 62324 # <--------- 12 hours default on contract start. self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) + # ------------------- initialize storage for DynArrays ------------------ + + _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS_128): if i == N_COINS_128: break - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + if i < N_COINS_128 - 1: + self.last_prices_packed.append(self.pack_2(10**18, 10**18)) - # --------------------------- initialize storage --------------------------- + self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) self.stored_balances.append(0) self.admin_balances.append(0) - # --------------------------- ERC20 stuff ---------------------------- + if _asset_types[i] == 3: + + _call_amount.append(10**convert(ERC20Detailed(_coins[i]).decimals(), uint256)) + _underlying_asset: address = ERC4626(_coins[i]).asset() + _scale_factor.append(10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256))) + + else: + + _call_amount.append(0) + _scale_factor.append(0) + + call_amount = _call_amount + scale_factor = _scale_factor + + # ----------------------------- ERC20 stuff ------------------------------ name = _name symbol = _symbol @@ -421,10 +439,13 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: elif asset_types[i] == 3: # ERC4626 - coin_decimals: uint256 = convert(ERC20Detailed(coins[i]).decimals(), uint256) - fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(10**coin_decimals) * 10**(18 - coin_decimals) - - rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) + # fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i] + # here: call_amount has ERC4626 precision, but the returned value is scaled up to 18 + # using scale_factor which is (18 - n) if underlying asset has n decimals. + rates[i] = unsafe_div( + rates[i] * ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i], + PRECISION + ) # 1e18 precision return rates diff --git a/contracts/mocks/ERC4626.vy b/contracts/mocks/ERC4626.vy new file mode 100644 index 00000000..5f7fd379 --- /dev/null +++ b/contracts/mocks/ERC4626.vy @@ -0,0 +1,270 @@ +# @version 0.3.10 +# From: https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy +from vyper.interfaces import ERC20 + +import ERC4626 as ERC4626 + +implements: ERC20 +implements: ERC4626 + +##### ERC20 ##### + +totalSupply: public(uint256) +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) + +NAME: immutable(String[10]) +SYMBOL: immutable(String[5]) +DECIMALS: immutable(uint8) + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + amount: uint256 + +event Approval: + owner: indexed(address) + spender: indexed(address) + allowance: uint256 + +##### ERC4626 ##### + +asset: public(immutable(ERC20)) + +event Deposit: + depositor: indexed(address) + receiver: indexed(address) + assets: uint256 + shares: uint256 + +event Withdraw: + withdrawer: indexed(address) + receiver: indexed(address) + owner: indexed(address) + assets: uint256 + shares: uint256 + + +@external +def __init__( + _name: String[10], + _symbol: String[5], + _decimals: uint8, + _asset: ERC20 +): + NAME = _name + SYMBOL = _symbol + DECIMALS = _decimals + asset = _asset + + +@view +@external +def name() -> String[10]: + return NAME + + +@view +@external +def symbol() -> String[5]: + return SYMBOL + + +@view +@external +def decimals() -> uint8: + return DECIMALS + + +@external +def transfer(receiver: address, amount: uint256) -> bool: + self.balanceOf[msg.sender] -= amount + self.balanceOf[receiver] += amount + log Transfer(msg.sender, receiver, amount) + return True + + +@external +def approve(spender: address, amount: uint256) -> bool: + self.allowance[msg.sender][spender] = amount + log Approval(msg.sender, spender, amount) + return True + + +@external +def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: + self.allowance[sender][msg.sender] -= amount + self.balanceOf[sender] -= amount + self.balanceOf[receiver] += amount + log Transfer(sender, receiver, amount) + return True + + +@view +@external +def totalAssets() -> uint256: + return asset.balanceOf(self) + + +@view +@internal +def _convertToAssets(shareAmount: uint256) -> uint256: + totalSupply: uint256 = self.totalSupply + if totalSupply == 0: + return 0 + + # NOTE: `shareAmount = 0` is extremely rare case, not optimizing for it + return shareAmount * asset.balanceOf(self) / totalSupply + + +@view +@external +def convertToAssets(shareAmount: uint256) -> uint256: + return self._convertToAssets(shareAmount) + + +@view +@internal +def _convertToShares(assetAmount: uint256) -> uint256: + totalSupply: uint256 = self.totalSupply + totalAssets: uint256 = asset.balanceOf(self) + if totalAssets == 0 or totalSupply == 0: + return assetAmount # 1:1 price + + # NOTE: `assetAmount = 0` is extremely rare case, not optimizing for it + return assetAmount * totalSupply / totalAssets + + +@view +@external +def convertToShares(assetAmount: uint256) -> uint256: + return self._convertToShares(assetAmount) + + +@view +@external +def maxDeposit(owner: address) -> uint256: + return max_value(uint256) + + +@view +@external +def previewDeposit(assets: uint256) -> uint256: + return self._convertToShares(assets) + + +@external +def deposit(assets: uint256, receiver: address=msg.sender) -> uint256: + shares: uint256 = self._convertToShares(assets) + asset.transferFrom(msg.sender, self, assets) + + self.totalSupply += shares + self.balanceOf[receiver] += shares + log Transfer(empty(address), receiver, shares) + log Deposit(msg.sender, receiver, assets, shares) + return shares + + +@view +@external +def maxMint(owner: address) -> uint256: + return max_value(uint256) + + +@view +@external +def previewMint(shares: uint256) -> uint256: + assets: uint256 = self._convertToAssets(shares) + + # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time + if assets == 0 and asset.balanceOf(self) == 0: + return shares # NOTE: Assume 1:1 price if nothing deposited yet + + return assets + + +@external +def mint(shares: uint256, receiver: address=msg.sender) -> uint256: + assets: uint256 = self._convertToAssets(shares) + + if assets == 0 and asset.balanceOf(self) == 0: + assets = shares # NOTE: Assume 1:1 price if nothing deposited yet + + asset.transferFrom(msg.sender, self, assets) + + self.totalSupply += shares + self.balanceOf[receiver] += shares + log Transfer(empty(address), receiver, shares) + log Deposit(msg.sender, receiver, assets, shares) + return assets + + +@view +@external +def maxWithdraw(owner: address) -> uint256: + return max_value(uint256) # real max is `asset.balanceOf(self)` + + +@view +@external +def previewWithdraw(assets: uint256) -> uint256: + shares: uint256 = self._convertToShares(assets) + + # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time + if shares == assets and self.totalSupply == 0: + return 0 # NOTE: Nothing to redeem + + return shares + + +@external +def withdraw(assets: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: + shares: uint256 = self._convertToShares(assets) + + # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time + if shares == assets and self.totalSupply == 0: + raise # Nothing to redeem + + if owner != msg.sender: + self.allowance[owner][msg.sender] -= shares + + self.totalSupply -= shares + self.balanceOf[owner] -= shares + + asset.transfer(receiver, assets) + log Transfer(owner, empty(address), shares) + log Withdraw(msg.sender, receiver, owner, assets, shares) + return shares + + +@view +@external +def maxRedeem(owner: address) -> uint256: + return max_value(uint256) # real max is `self.totalSupply` + + +@view +@external +def previewRedeem(shares: uint256) -> uint256: + return self._convertToAssets(shares) + + +@external +def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: + if owner != msg.sender: + self.allowance[owner][msg.sender] -= shares + + assets: uint256 = self._convertToAssets(shares) + self.totalSupply -= shares + self.balanceOf[owner] -= shares + + asset.transfer(receiver, assets) + log Transfer(owner, empty(address), shares) + log Withdraw(msg.sender, receiver, owner, assets, shares) + return assets + + +@external +def DEBUG_steal_tokens(amount: uint256): + # NOTE: This is the primary method of mocking share price changes + asset.transfer(msg.sender, amount) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py new file mode 100644 index 00000000..8e2bf6c3 --- /dev/null +++ b/tests/pools/test_erc4626_swaps.py @@ -0,0 +1,169 @@ +import boa +import pytest +from eth_utils import function_signature_to_4byte_selector + +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def asset(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20.vy", + "Asset", + "AST", + 8, # 8 decimals + ) + + +@pytest.fixture(scope="module") +def token_a(deployer, asset): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC4626.vy", + "Vault", + "VLT", + 8, # 8 decimals + asset.address, + ) + + +@pytest.fixture(scope="module") +def token_b(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Oracle.vy", + "Oracle", + "ORC", + 18, + 1006470359024000000, + ) + + +@pytest.fixture(scope="module") +def token_c(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20Rebasing.vy", + "Rebasing", + "RBSN", + 6, + True, + ) + + +@pytest.fixture(scope="module") +def pool_tokens(token_a, token_b, token_c): + return [token_a, token_b, token_c] + + +@pytest.fixture(scope="module") +def pool_erc20_tokens(asset, token_b, token_c): + return [asset, token_b, token_c] + + +@pytest.fixture(scope="module") +def asset_types(pool_tokens): + _asset_types = [] + for token in pool_tokens: + if "ERC20Oracle" in token.filename: + _asset_types.append(1) + elif "ERC20Rebasing" in token.filename: + _asset_types.append(2) + elif "ERC4626" in token.filename: + _asset_types.append(3) + else: + _asset_types.append(0) + return _asset_types + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + pool_tokens, + zero_address, + amm_interface, + asset_types, + set_pool_implementations, +): + pool_size = len(pool_tokens) + oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + for i in range(pool_size): + + if asset_types[i] == 1: + method_ids[i] = oracle_method_id + oracles[i] = pool_tokens[i].address + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def deposit_amounts(pool_erc20_tokens, token_a, bob): + _deposit_amounts = [] + for i, token in enumerate(pool_tokens): + _deposit_amount = 10**6 * 10 ** token.decimals() + if token.balanceOf(bob) < _deposit_amount: + mint_for_testing(bob, _deposit_amount, token, False) + + if i == 0: # erc4626 token + token.approve(token_a, 2**256 - 1, sender=bob) + token_a.deposit(_deposit_amount, bob, sender=bob) + + _deposit_amounts.append(_deposit_amount) + return _deposit_amounts + + +@pytest.fixture(scope="module") +def swap(empty_swap, bob, deposit_amounts, pool_tokens): + + for token in pool_tokens: + token.approve(empty_swap, 2**256 - 1, sender=bob) + + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +def test_swap(swap, charlie, pool_tokens): + + amount_in = 10**18 + i = 0 + j = 1 + if amount_in > pool_tokens[i].balanceOf(charlie): + mint_for_testing(charlie, 10**18, pool_tokens[i], False) + + pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + swap.exchange(i, j, amount_in, 0, sender=charlie) + + +def test_rebase(swap, charlie, bob, pool_tokens): + + amount_rewards = 10**4 * 10**18 + i = 1 + if amount_rewards > pool_tokens[i].balanceOf(charlie): + mint_for_testing(charlie, amount_rewards, pool_tokens[i], False) + + pool_tokens[i].transfer(swap, amount_rewards, sender=charlie) # <---- donate. + bob_lp_tokens = swap.balanceOf(bob) + swap.remove_liquidity(bob_lp_tokens, [0, 0, 0], sender=bob) # <--- should not revert diff --git a/tests/pools/test_specific_liquidity_operations.py b/tests/pools/test_specific_liquidity_operations copy.py similarity index 100% rename from tests/pools/test_specific_liquidity_operations.py rename to tests/pools/test_specific_liquidity_operations copy.py From d23f6e06f7be96dfb99c8d6d4c2b1636e14da332 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:20:53 +0200 Subject: [PATCH 200/337] add tests for erc4626 tokens --- contracts/main/CurveStableSwapMetaNG.vy | 12 +- contracts/main/CurveStableSwapNG.vy | 11 +- contracts/mocks/ERC20RebasingConditional.vy | 148 ++++++++++++++++++ contracts/mocks/ERC4626.vy | 10 +- tests/pools/test_erc4626_swaps.py | 57 +++++-- ... => test_specific_liquidity_operations.py} | 0 6 files changed, 206 insertions(+), 32 deletions(-) create mode 100644 contracts/mocks/ERC20RebasingConditional.vy rename tests/pools/{test_specific_liquidity_operations copy.py => test_specific_liquidity_operations.py} (100%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index d13bceee..20cff040 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -178,8 +178,6 @@ N_COINS: public(constant(uint256)) = 2 N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 -POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) - BASE_POOL: public(immutable(address)) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) @@ -187,7 +185,7 @@ BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) -asset_types: public(immutable(DynArray[uint8, MAX_COINS])) +asset_types: immutable(DynArray[uint8, MAX_COINS]) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -316,8 +314,6 @@ def __init__( asset_types = _asset_types rate_multipliers = _rate_multipliers - POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types - for i in range(MAX_COINS): if i < BASE_N_COINS: # Approval needed for add_liquidity operation on base pool in @@ -539,7 +535,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: for i in range(N_COINS_128): - if POOL_IS_REBASING_IMPLEMENTATION: + if 2 in asset_types: balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] else: balances_i = self.stored_balances[i] - self.admin_balances[i] @@ -604,7 +600,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens + assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, @@ -664,7 +660,7 @@ def exchange_underlying_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens + assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange_underlying( msg.sender, i, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 2baa8e6b..9ffaeae8 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,5 +1,5 @@ # @version 0.3.10 -#pragma optimize gas +#pragma optimize codesize """ @title CurveStableSwapNG @author Curve.Fi @@ -139,11 +139,9 @@ N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) PRECISION: constant(uint256) = 10 ** 18 -POOL_IS_REBASING_IMPLEMENTATION: public(immutable(bool)) - factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) -asset_types: public(immutable(DynArray[uint8, MAX_COINS])) +asset_types: immutable(DynArray[uint8, MAX_COINS]) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -264,7 +262,6 @@ def __init__( N_COINS_128 = convert(__n_coins, int128) rate_multipliers = _rate_multipliers - POOL_IS_REBASING_IMPLEMENTATION = 2 in _asset_types factory = Factory(msg.sender) @@ -469,7 +466,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: if i == N_COINS_128: break - if POOL_IS_REBASING_IMPLEMENTATION: + if 2 in asset_types: balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] else: balances_i = self.stored_balances[i] - self.admin_balances[i] @@ -534,7 +531,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not POOL_IS_REBASING_IMPLEMENTATION # dev: exchange_received not supported if pool contains rebasing tokens + assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, diff --git a/contracts/mocks/ERC20RebasingConditional.vy b/contracts/mocks/ERC20RebasingConditional.vy new file mode 100644 index 00000000..77ca22d4 --- /dev/null +++ b/contracts/mocks/ERC20RebasingConditional.vy @@ -0,0 +1,148 @@ +# @version ^0.3.9 + +""" +@notice Rebasing ERC20 mock with rebase by 1% on every transfer +@dev This is for testing only, it is NOT safe for use +@dev Based on stEth implementation +""" + + +event Transfer: + _from: indexed(address) + _to: indexed(address) + _value: uint256 + + +event Approval: + _owner: indexed(address) + _spender: indexed(address) + _value: uint256 + + +name: public(String[64]) +symbol: public(String[32]) +decimals: public(uint256) +allowances: HashMap[address, HashMap[address, uint256]] + +# <--- Rebase Parameters ---> +totalCoin: public(uint256) +totalShares: public(uint256) +shares: public(HashMap[address, uint256]) +IS_UP: immutable(bool) + +# asset type +asset_type: public(constant(uint8)) = 2 + + +@external +def __init__(_name: String[64], _symbol: String[32], _decimals: uint256, is_up: bool): + self.name = _name + self.symbol = _symbol + self.decimals = _decimals + IS_UP = is_up + + +@external +@view +def totalSupply() -> uint256: + # Rebase is pegged to total pooled coin + return self.totalCoin + + +@external +@view +def balanceOf(_user: address) -> uint256: + return self._get_coins_by_shares(self.shares[_user]) + + +@external +@view +def allowance(_owner: address, _spender: address) -> uint256: + return self.allowances[_owner][_spender] + + +@external +def transfer(_to: address, _value: uint256) -> bool: + _shares: uint256 = self._get_shares_by_coins(_value) + + self.shares[msg.sender] -= _shares + self.shares[_to] += _shares + log Transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from: address, _to: address, _value: uint256) -> bool: + _shares: uint256 = self._get_shares_by_coins(_value) + _shares = min(self.shares[_from], _shares) + # Value can be less than expected even if self.shares[_from] > _shares + _new_value: uint256 = self._get_coins_by_shares(_shares) + + self.shares[_from] -= _shares + self.shares[_to] += _shares + self.allowances[_from][msg.sender] -= _new_value + + log Transfer(_from, _to, _new_value) + return True + + +@external +def approve(_spender: address, _value: uint256) -> bool: + self.allowances[msg.sender][_spender] = _value + log Approval(msg.sender, _spender, _value) + return True + + +@internal +@view +def _share_price() -> uint256: + if self.totalShares == 0: + return 10 ** self.decimals + return self.totalCoin * 10 ** self.decimals / self.totalShares + + +@internal +@view +def _get_coins_by_shares(_shares: uint256) -> uint256: + return _shares * self._share_price() / 10 ** self.decimals + + +@internal +@view +def _get_shares_by_coins(_coins: uint256) -> uint256: + return _coins * 10 ** self.decimals / self._share_price() + + +@external +@view +def share_price() -> uint256: + return self._share_price() + + +@external +def rebase(): + if IS_UP: + self.totalCoin = self.totalCoin * 1000001 / 1000000 + else: + self.totalCoin = self.totalCoin * 999999 / 1000000 + + +@external +def set_total_coin(total_coin: uint256) -> bool: + assert self.totalShares != 0, "no shares" + + self.totalCoin = total_coin + return True + + +@external +def _mint_for_testing(_target: address, _value: uint256) -> bool: + _shares: uint256 = self._get_shares_by_coins(_value) + + self.totalCoin += _value + self.totalShares += _shares + self.shares[_target] += _shares + + log Transfer(empty(address), _target, _value) + + return True diff --git a/contracts/mocks/ERC4626.vy b/contracts/mocks/ERC4626.vy index 5f7fd379..52f2d657 100644 --- a/contracts/mocks/ERC4626.vy +++ b/contracts/mocks/ERC4626.vy @@ -1,6 +1,7 @@ # @version 0.3.10 # From: https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy from vyper.interfaces import ERC20 +from vyper.interfaces import ERC20Detailed import ERC4626 as ERC4626 @@ -16,6 +17,7 @@ allowance: public(HashMap[address, HashMap[address, uint256]]) NAME: immutable(String[10]) SYMBOL: immutable(String[5]) DECIMALS: immutable(uint8) +_DECIMALS_OFFSET: immutable(uint8) event Transfer: sender: indexed(address) @@ -57,6 +59,8 @@ def __init__( DECIMALS = _decimals asset = _asset + _DECIMALS_OFFSET = _decimals - ERC20Detailed(_asset.address).decimals() + @view @external @@ -113,8 +117,7 @@ def _convertToAssets(shareAmount: uint256) -> uint256: if totalSupply == 0: return 0 - # NOTE: `shareAmount = 0` is extremely rare case, not optimizing for it - return shareAmount * asset.balanceOf(self) / totalSupply + return shareAmount * asset.balanceOf(self) / (totalSupply) @view @@ -129,9 +132,8 @@ def _convertToShares(assetAmount: uint256) -> uint256: totalSupply: uint256 = self.totalSupply totalAssets: uint256 = asset.balanceOf(self) if totalAssets == 0 or totalSupply == 0: - return assetAmount # 1:1 price + return assetAmount * 10**convert(_DECIMALS_OFFSET, uint256) # 1:1 price - # NOTE: `assetAmount = 0` is extremely rare case, not optimizing for it return assetAmount * totalSupply / totalAssets diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 8e2bf6c3..4b501bc4 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -1,3 +1,5 @@ +import itertools + import boa import pytest from eth_utils import function_signature_to_4byte_selector @@ -5,6 +7,14 @@ from tests.utils.tokens import mint_for_testing +def mint_vault_tokens(deposit_amount, underlying_token, vault_contract, user): + + mint_for_testing(user, deposit_amount, underlying_token, False) + underlying_token.approve(vault_contract, 2**256 - 1, sender=user) + vault_contract.deposit(deposit_amount, user, sender=user) + return vault_contract.balanceOf(user) + + @pytest.fixture(scope="module") def asset(deployer): with boa.env.prank(deployer): @@ -23,7 +33,7 @@ def token_a(deployer, asset): "contracts/mocks/ERC4626.vy", "Vault", "VLT", - 8, # 8 decimals + 18, # 8 decimals asset.address, ) @@ -44,7 +54,7 @@ def token_b(deployer): def token_c(deployer): with boa.env.prank(deployer): return boa.load( - "contracts/mocks/ERC20Rebasing.vy", + "contracts/mocks/ERC20RebasingConditional.vy", "Rebasing", "RBSN", 6, @@ -122,16 +132,22 @@ def empty_swap( @pytest.fixture(scope="module") def deposit_amounts(pool_erc20_tokens, token_a, bob): _deposit_amounts = [] - for i, token in enumerate(pool_tokens): + for i, token in enumerate(pool_erc20_tokens): _deposit_amount = 10**6 * 10 ** token.decimals() if token.balanceOf(bob) < _deposit_amount: mint_for_testing(bob, _deposit_amount, token, False) if i == 0: # erc4626 token - token.approve(token_a, 2**256 - 1, sender=bob) - token_a.deposit(_deposit_amount, bob, sender=bob) + _deposit_amount = mint_vault_tokens(_deposit_amount, token, token_a, bob) _deposit_amounts.append(_deposit_amount) + + try: + assert token.balanceOf(bob) == _deposit_amount + except: # noqa: E722 + mint_for_testing(bob, _deposit_amount, token, False) + assert token.balanceOf(bob) == _deposit_amount + return _deposit_amounts @@ -140,21 +156,36 @@ def swap(empty_swap, bob, deposit_amounts, pool_tokens): for token in pool_tokens: token.approve(empty_swap, 2**256 - 1, sender=bob) - empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) return empty_swap -def test_swap(swap, charlie, pool_tokens): +@pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) +def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): - amount_in = 10**18 - i = 0 - j = 1 - if amount_in > pool_tokens[i].balanceOf(charlie): - mint_for_testing(charlie, 10**18, pool_tokens[i], False) + amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() + + if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): + if i != 0: + bal_before = pool_erc20_tokens[i].balanceOf(charlie) + mint_for_testing(charlie, amount_erc20_in, pool_erc20_tokens[i], False) + amount_in = pool_erc20_tokens[i].balanceOf(charlie) - bal_before + else: + amount_in = mint_vault_tokens(amount_erc20_in, pool_erc20_tokens[0], pool_tokens[0], charlie) pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) - swap.exchange(i, j, amount_in, 0, sender=charlie) + + if "RebasingConditional" in pool_tokens[i].filename: + pool_tokens[i].rebase() + + calculated = swap.get_dy(i, j, amount_in) + dy = swap.exchange(i, j, amount_in, int(0.99 * calculated), sender=charlie) + + try: + assert dy == calculated + except: # noqa: E722 + assert 2 in [i, j] # rebasing token balances can have wonky math + assert dy == pytest.approx(calculated) def test_rebase(swap, charlie, bob, pool_tokens): diff --git a/tests/pools/test_specific_liquidity_operations copy.py b/tests/pools/test_specific_liquidity_operations.py similarity index 100% rename from tests/pools/test_specific_liquidity_operations copy.py rename to tests/pools/test_specific_liquidity_operations.py From 3f0cd36cb5d2a5977721786b83d3b1a68ec750c0 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:40:06 +0200 Subject: [PATCH 201/337] add helper method to donate amount to vault --- tests/pools/test_erc4626_swaps.py | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 4b501bc4..de9b9675 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -15,6 +15,13 @@ def mint_vault_tokens(deposit_amount, underlying_token, vault_contract, user): return vault_contract.balanceOf(user) +def donate_to_vault(donation_amount, underlying_token, vault_contract, user): + + donation_amount = donation_amount * 10 ** underlying_token.decimals() + mint_for_testing(user, donation_amount, underlying_token, False) + underlying_token.transfer(vault_contract, donation_amount, sender=user) + + @pytest.fixture(scope="module") def asset(deployer): with boa.env.prank(deployer): @@ -188,6 +195,40 @@ def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): assert dy == pytest.approx(calculated) +@pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) +def test_donate_swap(swap, i, j, alice, charlie, pool_tokens, pool_erc20_tokens): + + amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() + + if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): + if i != 0: + bal_before = pool_erc20_tokens[i].balanceOf(charlie) + mint_for_testing(charlie, amount_erc20_in, pool_erc20_tokens[i], False) + amount_in = pool_erc20_tokens[i].balanceOf(charlie) - bal_before + else: + amount_in = mint_vault_tokens(amount_erc20_in, pool_erc20_tokens[0], pool_tokens[0], charlie) + + pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + + # rebase: + if "RebasingConditional" in pool_tokens[i].filename: + pool_tokens[i].rebase() + + # donate to vault: + donate_to_vault(10**5, pool_erc20_tokens[0], pool_tokens[0], alice) + + # calculate expected output and swap: + calculated = swap.get_dy(i, j, amount_in) + dy = swap.exchange(i, j, amount_in, int(0.99 * calculated), sender=charlie) + + # check: + try: + assert dy == calculated + except: # noqa: E722 + assert 2 in [i, j] # rebasing token balances can have wonky math + assert dy == pytest.approx(calculated) + + def test_rebase(swap, charlie, bob, pool_tokens): amount_rewards = 10**4 * 10**18 From ffe12f0b45e8dc2e7fe4324fafd0418abc92743a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:43:04 +0200 Subject: [PATCH 202/337] fix: remove deprecated var --- tests/conftest.py | 4 ++-- tests/pools/test_donation_get_D.py | 2 +- tests/pools/test_erc4626_swaps.py | 1 + tests/pools/test_exchange_received.py | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d32f8402..6b44bbb9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -181,7 +181,7 @@ def skip_by_token_type(request, pool_tokens): def skip_rebasing(request, swap): only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") if only_for_token_types: - if swap.POOL_IS_REBASING_IMPLEMENTATION(): + if 2 in swap._immutables.asset_types: pytest.skip("skipped because test includes rebasing tokens") @@ -209,7 +209,7 @@ def only_oracle(request, pool_tokens): def only_rebasing(request, swap): marker = request.node.get_closest_marker("contains_rebasing_tokens") if marker: - asset_types_contains_rebasing = swap.POOL_IS_REBASING_IMPLEMENTATION() + asset_types_contains_rebasing = 2 in swap._immutables.asset_types if not asset_types_contains_rebasing: pytest.skip("skipped because test excludes rebasing tokens") diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index d770a2b7..af2aa3dd 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -25,7 +25,7 @@ def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): # donate 1 wei and attempt adding liquidity: pool_tokens[0].transfer(swap, 10, sender=bob) - if swap.POOL_IS_REBASING_IMPLEMENTATION(): + if 2 in swap._immutables.asset_types: with boa.reverts(): # division by zero error expected for rebasing implementation swap.add_liquidity(amounts, 0, sender=bob) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index de9b9675..a54dd339 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -184,6 +184,7 @@ def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): if "RebasingConditional" in pool_tokens[i].filename: pool_tokens[i].rebase() + assert 2 in swap._immutables.asset_types calculated = swap.get_dy(i, j, amount_in) dy = swap.exchange(i, j, amount_in, int(0.99 * calculated), sender=charlie) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index d32801f8..76369467 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -207,7 +207,7 @@ def test_exchange_underlying_not_received(bob, swap, sending, receiving): @pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): - if swap.POOL_IS_REBASING_IMPLEMENTATION(): + if 2 in swap._immutables.asset_types: with boa.reverts(): transfer_and_swap(swap, sending, receiving, True) @@ -216,6 +216,6 @@ def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - if swap.POOL_IS_REBASING_IMPLEMENTATION(): + if 2 in swap._immutables.asset_types: with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) From b5a073c0a8eb1e6281a23d029b7995c2dec261ac Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 12 Oct 2023 18:01:48 +0200 Subject: [PATCH 203/337] add helper fn for minting tokens --- tests/pools/test_erc4626_swaps.py | 43 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index a54dd339..7b290238 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -22,6 +22,23 @@ def donate_to_vault(donation_amount, underlying_token, vault_contract, user): underlying_token.transfer(vault_contract, donation_amount, sender=user) +def mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i): + + amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() + + if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): + if i != 0: + bal_before = pool_erc20_tokens[i].balanceOf(charlie) + mint_for_testing(charlie, amount_erc20_in, pool_erc20_tokens[i], False) + amount_in = pool_erc20_tokens[i].balanceOf(charlie) - bal_before + else: + amount_in = mint_vault_tokens(amount_erc20_in, pool_erc20_tokens[0], pool_tokens[0], charlie) + + pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + + return amount_in + + @pytest.fixture(scope="module") def asset(deployer): with boa.env.prank(deployer): @@ -168,19 +185,9 @@ def swap(empty_swap, bob, deposit_amounts, pool_tokens): @pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) -def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): +def test_swap(swap, i, j, charlie, pool_tokens, mint_tokens): - amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() - - if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): - if i != 0: - bal_before = pool_erc20_tokens[i].balanceOf(charlie) - mint_for_testing(charlie, amount_erc20_in, pool_erc20_tokens[i], False) - amount_in = pool_erc20_tokens[i].balanceOf(charlie) - bal_before - else: - amount_in = mint_vault_tokens(amount_erc20_in, pool_erc20_tokens[0], pool_tokens[0], charlie) - - pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + amount_in = mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i) if "RebasingConditional" in pool_tokens[i].filename: pool_tokens[i].rebase() @@ -199,17 +206,7 @@ def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): @pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) def test_donate_swap(swap, i, j, alice, charlie, pool_tokens, pool_erc20_tokens): - amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() - - if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): - if i != 0: - bal_before = pool_erc20_tokens[i].balanceOf(charlie) - mint_for_testing(charlie, amount_erc20_in, pool_erc20_tokens[i], False) - amount_in = pool_erc20_tokens[i].balanceOf(charlie) - bal_before - else: - amount_in = mint_vault_tokens(amount_erc20_in, pool_erc20_tokens[0], pool_tokens[0], charlie) - - pool_tokens[i].approve(swap, 2**256 - 1, sender=charlie) + amount_in = mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i) # rebase: if "RebasingConditional" in pool_tokens[i].filename: From b4da1e93daafe32c34ee6aefa2e05a0eb02d91f0 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:26:17 +0200 Subject: [PATCH 204/337] add feedback from Ivan --- contracts/main/CurveStableSwapMetaNG.vy | 15 +++++++++------ contracts/main/CurveStableSwapNG.vy | 14 ++++++++------ tests/pools/test_erc4626_swaps.py | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 20cff040..7b69d980 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -20,21 +20,24 @@ 3. ERC20 tokens that rebase (either positive or fee on transfer) 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) Note: Oracle precision _must_ be 10**18. + 5. ERC4626 tokens with arbitrary precision (<=18) of Vault token and underlying + asset. Additional features include: 1. Adds oracles based on AMM State Price (and _not_ last traded price). State prices are calculated _after_ liquidity operations, using bonding - curve math. Also adds an exponential moving average oracle for D. - 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred + curve math. + 2. Adds an exponential moving average oracle for D. + 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 3. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output + 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 4. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 @@ -584,7 +587,7 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _receiver: address, + _receiver: address = msg.sender, ) -> uint256: """ @notice Perform an exchange between two coins without transferring token in @@ -648,7 +651,7 @@ def exchange_underlying_received( j: int128, _dx: uint256, _min_dy: uint256, - _receiver: address, + _receiver: address = msg.sender, ) -> uint256: """ @notice Perform an exchange between two underlying coins diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 9ffaeae8..dc9a704f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -20,20 +20,22 @@ 3. ERC20 tokens that rebase (either positive or fee on transfer) 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) Note: Oracle precision _must_ be 10**18. + 5. ERC4626 tokens with arbitrary precision (<=18) of Vault token and underlying + asset. Additional features include: - 1. Adds price oracles based on AMM State Price (and _not_ last traded price) - and a TVL oracle based on D. - 2. `exchange_received`: swaps that expect an ERC20 transfer to have occurred + 1. Adds price oracles based on AMM State Price (and _not_ last traded price). + 2. Adds TVL oracle based on D. + 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred prior to executing the swap. Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) then calling `exchange_received` will REVERT. b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) then this is an incorrect implementation and rebases can be stolen. - 3. Adds `get_dx`: Similar to `get_dy` which returns an expected output + 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 4. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. """ from vyper.interfaces import ERC20 @@ -515,7 +517,7 @@ def exchange_received( j: int128, _dx: uint256, _min_dy: uint256, - _receiver: address, + _receiver: address = msg.sender, ) -> uint256: """ @notice Perform an exchange between two coins without transferring token in diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 7b290238..0ae7af37 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -185,7 +185,7 @@ def swap(empty_swap, bob, deposit_amounts, pool_tokens): @pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) -def test_swap(swap, i, j, charlie, pool_tokens, mint_tokens): +def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): amount_in = mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i) From a13a8b8e553f932252f0a5e14df4119947733eee Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:08:19 +0200 Subject: [PATCH 205/337] separate base pool setup from deploying infra --- scripts/deploy.py | 34 ++++++++++++++++++++-------------- scripts/deployment_utils.py | 4 ++-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/scripts/deploy.py b/scripts/deploy.py index bce71d92..231796c2 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -64,20 +64,26 @@ def deploy_infra(network, account): factory.set_pool_implementations(0, plain_blueprint_contract, **deploy_utils._get_tx_params()) factory.set_metapool_implementations(0, meta_blueprint_contract, **deploy_utils._get_tx_params()) - # -------------------------- Add base pools -------------------------- - - logger.info("Setting up base pools ...") - base_pool_data = deploy_utils.base_pool_list[network] - if base_pool_data: # check if network has base pools: - for data in base_pool_data: - factory.add_base_pool( - data.pool, - data.lp_token, - data.coins, - data.asset_types, - data.n_coins, - **deploy_utils._get_tx_params(), - ) + +@cli.command(cls=NetworkBoundCommand) +@network_option() +@account_option() +@click.option("--factory", required=True, type=str) +def set_up_base_pools(network, account, factory): + + logger.info("Setting up base pools ...") + base_pool_data = deploy_utils.base_pool_list[network] + if base_pool_data: # check if network has base pools: + for data in base_pool_data: + factory.add_base_pool( + data.pool, + data.lp_token, + data.coins, + data.asset_types, + data.n_coins, + **deploy_utils._get_tx_params(), + ) + logger.info(f"Added {data.pool} to factory") @cli.command(cls=NetworkBoundCommand) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 2073d5db..066891e6 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -99,8 +99,8 @@ class CurveNetworkSettings: fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", ), "gnosis:mainnet": CurveNetworkSettings( - dao_ownership_contract="", # <--- need to deploy sidechain ownership contract # noqa: E501 - fee_receiver_address="", # <--- need to deploy sidechain pool proxy + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- need to deploy sidechain ownership contract # noqa: E501 + fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- need to deploy sidechain pool proxy ), "fantom:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- thin proxy # noqa: E501 From 5fc9b099d3cf495a17aca6263e1aa85268841208 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:29:27 +0200 Subject: [PATCH 206/337] add sepolia deployment and boa deployment scripts --- .gitignore | 1 + README.MD | 15 +- ape-config.yaml | 4 +- contracts/main/CurveStableSwapFactoryNG.vy | 6 +- .../main/CurveStableSwapFactoryNGHandler.vy | 3 +- contracts/main/CurveStableSwapMetaNG.vy | 34 +- contracts/main/CurveStableSwapNG.vy | 29 +- contracts/main/CurveStableSwapNGMath.vy | 5 +- contracts/main/CurveStableSwapNGViews.vy | 5 +- contracts/main/LiquidityGauge.vy | 5 +- poetry.lock | 2845 +++++++++-------- pyproject.toml | 2 +- scripts/deploy_infra.py | 89 + scripts/deployment_utils.py | 1 + 14 files changed, 1722 insertions(+), 1322 deletions(-) create mode 100644 scripts/deploy_infra.py diff --git a/.gitignore b/.gitignore index be85aaec..81776345 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,4 @@ scripts/experiments/get_p.py .python-version todo.txt AuditorComments.md +set_env.py diff --git a/README.MD b/README.MD index 8a6e8cd2..86a1edce 100644 --- a/README.MD +++ b/README.MD @@ -1,10 +1,19 @@ # Stableswap NG -Permissionless deployment of Curve metapools. +Permissionless deployment of Curve Stableswap plain and metapools. Supports up to 8 coins for plain pools and 2 coins for metapools. Supports: rate-oraclised tokens (e.g. wstETH), ERC4626 (sDAI), rebasing (stETH), and plain (WETH:stETH) pools. Does not support native tokens. -# Wen? +For integrators: check exchange_received. That should improve your pathing significantly. Be aware that if a pool contains rebasing tokens, this method is intentionally disabled. -![STOP THE WEN!](./you_shall_not_wen.jpeg) +# Deployments + +## Sepolia + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443](https://sepolia.etherscan.io/address/0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0](https://sepolia.etherscan.io/address/0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd](https://sepolia.etherscan.io/address/0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e](https://sepolia.etherscan.io/address/0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e#code) +5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) +6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x8a00365ae28d75b92ec695d5a041b744f140438d](https://sepolia.etherscan.io/address/0x8a00365ae28d75b92ec695d5a041b744f140438d#code) ## Overview diff --git a/ape-config.yaml b/ape-config.yaml index 5422b784..ca53c351 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -15,9 +15,7 @@ plugins: default_ecosystem: ethereum # vyper: -# default_version: paris # enable for non PUSH0 evm networks -# ethereum: -# evm_version: shanghai +# evm_version: shanghai hardhat: port: auto diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 393a0ffd..f12221c2 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,4 +1,5 @@ -# @version 0.3.10 +# pragma version 0.3.10 +# pragma evm-version shanghai """ @title CurveStableswapFactoryNG @author Curve.Fi @@ -721,6 +722,9 @@ def add_base_pool( @notice Add a base pool to the registry, which may be used in factory metapools @dev 1. Only callable by admin 2. Rebasing tokens are not allowed in the base pool. + 3. Do not add base pool which contains native tokens (e.g. ETH). + 4. As much as possible: use standard ERC20 tokens. + Should you choose to deviate from these recommendations, audits are advised. @param _base_pool Pool address to add @param _asset_types Asset type for pool, as an integer """ diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index 648ff5ac..13250a18 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,4 +1,5 @@ -# @version 0.3.10 +# pragma version 0.3.10 +# pragma evm-version shanghai """ @title CurveStableswapFactoryNGHandler @author Curve.Fi diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 7b69d980..b2ba6a5e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,5 +1,6 @@ -# @version 0.3.10 -#pragma optimize codesize +# pragma version 0.3.10 +# pragma optimize codesize +# pragma evm-version shanghai """ @title CurveStableSwapMetaNG @author Curve.Fi @@ -10,10 +11,21 @@ exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the nth coin index of the base pool. Asset Types: - 0. Standard ERC20 token with no additional features + 0. Standard ERC20 token with no additional features. + Note: Users are advised to do careful due-diligence on + ERC20 tokens that they interact with, as this + contract cannot differentiate between harmless and + malicious ERC20 tokens. 1. Oracle - token with rate oracle (e.g. wstETH) - 2. Rebasing - token with rebase (e.g. stETH) + Note: Oracles may be controlled externally by an EOA. Users + are advised to proceed with caution. + 2. Rebasing - token with rebase (e.g. stETH). + Note: Users and Integrators are advised to understand how + the AMM contract works with rebasing balances. 3. ERC4626 - token with convertToAssets method (e.g. sDAI). + Note: Some ERC4626 implementations may be susceptible to + Donation/Inflation attacks. Users are advised to + proceed with caution. Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -37,7 +49,8 @@ 4. Adds `get_dx`, `get_dx_underlying`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. This can cause very + slight discrepancies between calculated fees and realised fees. """ from vyper.interfaces import ERC20 @@ -336,7 +349,7 @@ def __init__( factory = Factory(msg.sender) - A: uint256 = _A * A_PRECISION + A: uint256 = unsafe_mul(_A, A_PRECISION) self.initial_A = A self.future_A = A self.fee = _fee @@ -739,17 +752,20 @@ def add_liquidity( _dynamic_fee_i: uint256 = 0 # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + # unsafe math is safu here: + base_fee: uint256 = unsafe_div(unsafe_mul(self.fee, N_COINS), 4) for i in range(N_COINS_128): ideal_balance = D1 * old_balances[i] / D0 new_balance = new_balances[i] + # unsafe math is safu here: if ideal_balance > new_balance: - difference = ideal_balance - new_balance + difference = unsafe_sub(ideal_balance, new_balance) else: - difference = new_balance - ideal_balance + difference = unsafe_sub(new_balance, ideal_balance) # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index dc9a704f..889dca04 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,19 +1,29 @@ -# @version 0.3.10 -#pragma optimize codesize +# pragma version 0.3.10 +# pragma optimize codesize +# pragma evm-version shanghai """ @title CurveStableSwapNG @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved @notice Stableswap implementation for up to 8 coins with no rehypothecation, - i.e. tokens are not deposited into other contracts. Supports only - token pairs that are similarly priced. - The Pool contract also records exponential moving averages for coins - 1, 2 and 3 relative to coin 0. + i.e. the AMM does not deposit tokens into other contracts. The Pool contract also + records exponential moving averages for coins relative to coin 0. @dev Asset Types: - 0. Standard ERC20 token with no additional features + 0. Standard ERC20 token with no additional features. + Note: Users are advised to do careful due-diligence on + ERC20 tokens that they interact with, as this + contract cannot differentiate between harmless and + malicious ERC20 tokens. 1. Oracle - token with rate oracle (e.g. wstETH) - 2. Rebasing - token with rebase (e.g. stETH) + Note: Oracles may be controlled externally by an EOA. Users + are advised to proceed with caution. + 2. Rebasing - token with rebase (e.g. stETH). + Note: Users and Integrators are advised to understand how + the AMM contract works with rebasing balances. 3. ERC4626 - token with convertToAssets method (e.g. sDAI). + Note: Some ERC4626 implementations may be susceptible to + Donation/Inflation attacks. Users are advised to + proceed with caution. Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). @@ -35,7 +45,8 @@ 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected input of coin[i] for an output amount of coin[j]. - 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. This can cause very + slight discrepancies between calculated fees and realised fees. """ from vyper.interfaces import ERC20 diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 78478b7f..bc2bd55d 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,5 +1,6 @@ -# @version 0.3.10 -#pragma optimize gas +# pragma version 0.3.10 +# pragma optimize gas +# pragma evm-version shanghai """ @title CurveStableSwapNGMath @author Curve.Fi diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 23fd1aa1..26b40eed 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,6 +1,7 @@ -# @version 0.3.10 +# pragma version 0.3.10 +# pragma evm-version shanghai """ -@title CurveStableSwap2NG +@title CurveStableSwapNGViews @author Curve.Fi @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved @notice Auxiliary contract for Stableswap-NG containing utility methods for diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 100ce72e..a76496b7 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -1,5 +1,6 @@ -# @version 0.3.10 -#pragma optimize gas +# pragma version 0.3.10 +# pragma optimize gas +# pragma evm-version shanghai """ @title LiquidityGaugeV6 @author Curve.Fi diff --git a/poetry.lock b/poetry.lock index 1ba13f0e..4d684944 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,98 +2,98 @@ [[package]] name = "aiohttp" -version = "3.8.5" +version = "3.8.6" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.6" files = [ - {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, - {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, - {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, - {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, - {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, - {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, - {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, - {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, - {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, - {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, - {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, - {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, - {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, - {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, - {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, - {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, - {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, - {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, - {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, - {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, - {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, - {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, - {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, - {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, - {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, - {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, - {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, - {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, - {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, - {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, - {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, + {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, + {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, + {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, + {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, + {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, + {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, + {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, + {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, + {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, + {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, + {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, + {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, + {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, + {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, + {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, + {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, + {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, + {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, + {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, + {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, + {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, + {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, + {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, + {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, + {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, + {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, + {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, + {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, ] [package.dependencies] @@ -122,6 +122,17 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "appnope" version = "0.1.3" @@ -135,17 +146,17 @@ files = [ [[package]] name = "asttokens" -version = "2.2.1" +version = "2.4.0" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, - {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, + {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, + {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, ] [package.dependencies] -six = "*" +six = ">=1.12.0" [package.extras] test = ["astroid", "pytest"] @@ -204,113 +215,133 @@ files = [ [[package]] name = "bitarray" -version = "2.7.6" +version = "2.8.2" description = "efficient arrays of booleans -- C extension" optional = false python-versions = "*" files = [ - {file = "bitarray-2.7.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:82816e361303817ac79d6870d51a3c7f1f343e816a0b1d885b713389d9bba425"}, - {file = "bitarray-2.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b798d37fcd8e78e381660a65c434c3f60713c6f076fcecfd22544ec46d7416e6"}, - {file = "bitarray-2.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b133a161bb4426e0bdd0e53be979dfdc5f63f14043b8e194d1fba6391e1672"}, - {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aafd10decf8940e8f2d21e25b32b21ae9fd1aa58f168ea97f0f82adb0f6aa33a"}, - {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:913e767a3d9a50d2213f5e22db4e87b8a29acc38be5001d29b6be42b1361b812"}, - {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ffcde9d694bfb34f82e6739b33fd267bad25e6b4042b43bfffbe3ba31184318"}, - {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be9ffbb6dd777233331b2fc08d30957cb01f86e2ee11aa5d7652158fa3b795"}, - {file = "bitarray-2.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:190d7e018565f0745c76ced5060c2a0a218efb5b3e5df71f8a0da5661decaaf7"}, - {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab8fc4de49c75ef358fa9a30a367a163f62b92048a3da084500b88283aba47a6"}, - {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1211d16de1814f910fc7f2de74930ea22b4feb9b699eed5ceaef8ed14736fe50"}, - {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0f8e85acce0c3434776fc82819dfd09d9003595456f838d50b1009d6ac0be031"}, - {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e095f92ba7c6424fc4918f28ddc998e1628bbf83bd5b0405c08c5591f74560a3"}, - {file = "bitarray-2.7.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98959be5e864bed9c4fcd90d47d96cf5250d86a2a22b39ec41514c54c80f29bd"}, - {file = "bitarray-2.7.6-cp310-cp310-win32.whl", hash = "sha256:c6a13db2090c16a8753a6b6fb0e5ac0c7ece1930d4fa85c20e885aec550bceb3"}, - {file = "bitarray-2.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:31b2855bd566c965ec00ca5629a2637786e0605005826be0f6f192f3756f2aa2"}, - {file = "bitarray-2.7.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fd489d5b82679528fe096084c4e6976199874edc3c126bb41cd668987bd784"}, - {file = "bitarray-2.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:557cc0858e5171b542ed48bbb58ae83bc65da82fb3fd88dafd21e33ba1b685d3"}, - {file = "bitarray-2.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e40be8a46062ff89c07fe00a0ea7e06a069805cf1f9cb57196a89f52fdcaf80"}, - {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e1abda299b516573aa916e4c58bb870dd55f17dd544041d9290cb1c005be30"}, - {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6897bfadf3077685c73cd6ca60ebeaaa40b3bbe060f38f0a6c577bebe37935f4"}, - {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a9347acb24a0ee50cc932c52f58337d5b9503bb5b78f435cf3a47cd7031d86"}, - {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f16a5e4fd0614d43c1bd5bd0887deca1fcf292253662bce040c169492005fd8a"}, - {file = "bitarray-2.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a871ee6f460d03febdd1577ddab66c7f297a43eff3293ee4cb34f6eb5cacb6"}, - {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e373bd92a6caf9bb6bb2918e3f6e64ba10045d62c2e376d80d0ec062dcdb19ef"}, - {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ca4dc98b56461d3656f3219cfcc4a6845479ab7e6cdc134c7fa2615336c55f76"}, - {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:2b3527f41bbfd4b8b64b9d7541cd9f8234b0d5a477e59b8c7f666f6f9635ea09"}, - {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d77b8ed242fa4b70a609e0bf81dd1f2db161ff4a082f7dfe233b491fe4c0c39"}, - {file = "bitarray-2.7.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:efe64997aeabedbaf24543020cc5c672d00cb373e126c12fe7e21e128f548001"}, - {file = "bitarray-2.7.6-cp311-cp311-win32.whl", hash = "sha256:5ac235eb59737bffce44d605a2c1af47e56e28a98bc7c9c763587e8bd8c5ebe0"}, - {file = "bitarray-2.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:2c2bbb73b8dbc52dd26ae40ae83f2e863fc48db6c37ea7fbfd5a750ec62c0a30"}, - {file = "bitarray-2.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d15685830b607088f040ec512d045ab762678d33e813e882a260ae3ee3d381db"}, - {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94333a9f419ddf2f92ddbdb34a10a4b2c13e346f077511a35de87a0acea4289f"}, - {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cff6588ec89b276bab617b15778b20aeb5124df1e04b1c0b5364e9cb8dce6a7f"}, - {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60acdb34def45f72f50a2277763b2a979e4c42dcb4c183a3a9a75bfe195e0b65"}, - {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88bfe996b1adb811690333523e2500d700f6d3dfb282b0b77194e8ff1d168873"}, - {file = "bitarray-2.7.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56e6e68d0a508b0a2bd81840e31c41f4858585cd72f192264c4f0f8f48fd0c1"}, - {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e1ec302eda30e87793bd31c16c18a92118a1072831200247d0a8430e095e9563"}, - {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d054a65a9b3848744e49f195ef7399841c545c05ad11854982e271331a4577c"}, - {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:26e4f99f5bdb20c9275dcb5bde44aaae74c52224f328bf1f691be43d6eaccc2b"}, - {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:523de6f222c310a865f4070f79b2ad0e0b35b9f26b34ee9f93a3dc8ad293dbfb"}, - {file = "bitarray-2.7.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bcc8bd9fe25b48cff1702a6ef700f7a7726c8084be8edcae67a1df0d23caa822"}, - {file = "bitarray-2.7.6-cp36-cp36m-win32.whl", hash = "sha256:0efcd03263f70f76298e56b6c5ed454ba89c01777ac25a790725bc063c6170b9"}, - {file = "bitarray-2.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:31c118dcaaad605c3d8ef953c79554f4822e454e85649dd9fe82e3fcd5017eef"}, - {file = "bitarray-2.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:216c113c68523b0d3a0f98c5d12236a1998b5c3bf79bfa17324368bf9d20cd26"}, - {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4451de47a3b2b378f03bc12d4cf49e64895dc263befee2af38420a91e38d4f9"}, - {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ad4c0e7e20c7384a111ce1883b91e203fc7950a6d4bb3ccdad23422b359ab4a"}, - {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f57e3e6362d2ca7ab797e423a2d5ed1934b8599d197097458c714cf107f4dc4a"}, - {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f21b59a436ec2f8226c397292e24f9dfb9c3eba904038f50778f2365e68fdea"}, - {file = "bitarray-2.7.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae83573d9f30f085755c7ba519f0498ecb773a0f2a7e428ed2a33324975e1cec"}, - {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5f36666371fcf8c1adaa0184b5762a7ce8310fdd33f5c39bf085c6dfe5fb0699"}, - {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5df77a37750fff8a107fba2e7adb887d171ae923306fa9a6c6c6c1a22c73da7f"}, - {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:08abec79d033ee10d6f4cb2fa9aec952d18eb06ce034b9cdf0cd8e6f2d023254"}, - {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:790aa2272aa6a2b792404a9f672dd136801a3a3860f74298c2542f63a206ed49"}, - {file = "bitarray-2.7.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95749278b59247a2a5779f1a0fc216f110eea1aa8af330609b8bdb5f8f55b78f"}, - {file = "bitarray-2.7.6-cp37-cp37m-win32.whl", hash = "sha256:5ebb24415e5838a729484ae698e1a17e27b826d2691d5cc6d4dc44a90f6a89e6"}, - {file = "bitarray-2.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:79da57026eb2c6b266c55d578b607f71d2eb9c5ae1bb3b32232d7dae059d63ed"}, - {file = "bitarray-2.7.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e9065cbc14ac490e4b1b5520ef33d61e9bdb41d066c7f18b20429c3025ea07e"}, - {file = "bitarray-2.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e6f228712a5696f3e13048894fc1f6d70d32421e154476b74595b0309a9d9dcf"}, - {file = "bitarray-2.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b803225158bbe17384e0a9f15cffecb5ae019b4d225a21887670ef20acfa4c8"}, - {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc2fd90298cb5e4c5882a8d8f4b39db23280a94387ba10ca266c311135ca57a"}, - {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72f638b6e56480c588580d10e3185dc9770d66a2bee49735ae6ac4277eb701da"}, - {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1802d92ba8b97a758f61f6aef8324c7705fe35a4b32ce9ed1569a8ddb43f0e33"}, - {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cdcd0b5f6952ed7187f46a7f0ea72436ddbd7111af2263adcbfe2850d0c26e7"}, - {file = "bitarray-2.7.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db83ad25c3ea908f1c983b4b28ab5b9408a18962fa21724a5d1d65887842462d"}, - {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98afc87b14868bb51f478f01c4d1578a190efb60c1b44dd7fc6dea7a61764889"}, - {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2ad866b82dd457239e2d0de864a09cc6efd4ccd2e0706314368835773e2b6018"}, - {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:c9778c56a4f9cda3c61941ea95db279b52c38e9b9ad174d52fd7174f9ec44c46"}, - {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:da810a6704723bbaae3d9d5a0c552c52e15f2b97aff599c5243464e04a9bc5ac"}, - {file = "bitarray-2.7.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e38fe19d4f6ddc669f8a13fa21b482ec72bc32d2def6b97018dba4db53823848"}, - {file = "bitarray-2.7.6-cp38-cp38-win32.whl", hash = "sha256:17c13936465e4dd2f4b58ce93f4d1fc92a684f1870830581bf950991e1e56eaa"}, - {file = "bitarray-2.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:732642e9284a7dbaca00d8a4837f997462fc01e8207f2be598e179c3b51e20e2"}, - {file = "bitarray-2.7.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1b8f439bf3939133ca2a12bfdc6fc2408b50099fd8fec88e5829f1eb9d49d636"}, - {file = "bitarray-2.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97496cca479e2b30a5c5354359f1fb85c4a0678b8d651656cc96a6ba3028bc94"}, - {file = "bitarray-2.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7a8d697e201a2f9b16d292d7fa066b9bbc0123abd9058885e408db984a1b4776"}, - {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:249e5d52785de2615264e579e25d85038e4bea9797e24e47b577e614c1187a2d"}, - {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e1da9ebd148389bce250403c02ef1f1495984612db534072b0aa101fd051a0b"}, - {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c237144123f1eb563c305d70634a97729716bfcd841f1519c33396e62d8ea3bd"}, - {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e90774f5453c610a0b62f6d235120c459c1dc0f2c7e8b4871a14f2dcf4d74cc"}, - {file = "bitarray-2.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07ac65cae21d79a4642a85109e40da48903c58fd14b7e42f31c48c37b7b8a37a"}, - {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eeeb25912fc4832546d328032e345d281720c2c73effd2234feb24045cfb10e"}, - {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37429158856fe0548786910ebc16e56606a60f01946f746cb7a6a78acf787711"}, - {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:5ecdb1f22f9aae4405393cf845b1a43bbf786536fa673aae92745508ca8c84ef"}, - {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1bc7466820fae5376e2c0c9592e67ffd2a5fb1ab261942357ba23b5252b5eae5"}, - {file = "bitarray-2.7.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:900cc391b38bf098f30ae5847c2df30f413b9311f27f13ced1818cbc28da928c"}, - {file = "bitarray-2.7.6-cp39-cp39-win32.whl", hash = "sha256:17d2df5517ab961b6ce679c050884141439695a931539597fea76933891f04a0"}, - {file = "bitarray-2.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:66ebfee12371d9cf7f59eb8964fcb1e711f075df6a2e29880c9aa8b55fa5bce4"}, - {file = "bitarray-2.7.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17df9b29105beec4dc4a489041783ddfe9de2628706bd4255863acfe3a78e648"}, - {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffdb9106fd0d36b0a87db9903e3078877a9cdde98e064b76f24f6d8eb46067e6"}, - {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689859e796d39d0151d1d7a0f50b8119547cbd76d815884e8d8ff5e7e3ce8244"}, - {file = "bitarray-2.7.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bff837274244e1dcf57e43820ccd8f95736ab30e7f5f0c79e81f655717060d37"}, - {file = "bitarray-2.7.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ffd1d8edb67b8ca264e033a349b611426b5b84c3eec410aeb6df67b784450131"}, - {file = "bitarray-2.7.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:915abc5db73dd2e15182bc2b281e0aa565124068a6f36f8237d3479804478ea8"}, - {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90a246583ff481c869227058988f5d2cfe5dd5a6cf76596b6e8de88cc10d440a"}, - {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24f7804bd40deba52fb14c5c1510273e6e4a084ef7c9896a30bd9ee5353dbc40"}, - {file = "bitarray-2.7.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e4f09d684337e33f6ad24bf4cda984de07b969865bfe2a8fd556ee1e1581cc"}, - {file = "bitarray-2.7.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bbb093c86e2c747ba3e81b01924353d982d4f6b62cce76a5eb1eb157cdf499e9"}, - {file = "bitarray-2.7.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13734e42f83a7ef75c876458619e11863f1d60644c83ff9a5ce21abf2935f331"}, - {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439459b58ff0cd29883445662c5d80cee109c4ce5984001798bb74a31b1ac9e"}, - {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952170c03bc415719156594aed10c7b20ce7d4389551f6bc89969e23486d245c"}, - {file = "bitarray-2.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a13774c45e9f59941b374a0e489f6660fcb4d027005748036a829cfab097c14"}, - {file = "bitarray-2.7.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2d6a93eb2af22deba0e6f7353a68457fa04f933d59c6a0ca420759c0527850cd"}, - {file = "bitarray-2.7.6.tar.gz", hash = "sha256:3807f9323c308bc3f9b58cbe5d04dc28f34ac32d852999334da96b42f64b5356"}, + {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525eda30469522cd840a11ba866d0616c132f6c4be8966a297d7545e97fcb822"}, + {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3d9730341c825eb167ca06c9dddf6ad4d1b4e71ea7da73cc8c5139fcb5e14ca"}, + {file = "bitarray-2.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad8f8c39c8df184e346184699783f105755003662f0dbe1233d9d9849650ab5f"}, + {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8d08330d250df47088c13683322083afbdfafdc31df205616506d6b9f068f"}, + {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f19ccba8a6ddf1382b0fb4fb8d4e1330e4a1b148e5d198f0981ba2a97c3492"}, + {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4db2e0f58153a376d9a14873e342d507ca32640640284cddf3c1e74a65929477"}, + {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b3c27aeea1752f0c1df1e29115e4b6f0249173d71e53c5f7e2c821706f028b"}, + {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef23f62b3abd287cf368341540ef2a81c86b48de9d488e182e63fe24ac165538"}, + {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d79fd3c58a4dc71ffd0fc55982a9a2079fe94c76ccff2777092f6107d6a049a"}, + {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8528c59d3d3df6618777892b60435022d8917de9ea32933d439c7ffd24437237"}, + {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c35bb5fe018fd9c42be3c28e74dc7dcfae471c3c6689679dbd0bd1d6dc0f51b7"}, + {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:232e8faa8e624f3eb0552a636ebe745cee00480e0e56ad62f17808d281838f2e"}, + {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:945e97ad2bbf7885426f39641a735a31fd4ca2e84e4d0cd271d9880372d6eae1"}, + {file = "bitarray-2.8.2-cp310-cp310-win32.whl", hash = "sha256:88c2d427ab1b20f220c1d53171b0691faa8f0a219367d84e859f1001e90ceefc"}, + {file = "bitarray-2.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:f7c5745e0f96c2c16c03c7540dbe26f3b62ddee63059be0a014156933f054024"}, + {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a610426251d1340baa4d8b7942d2cbfe6a1e20b92c66817ab582e0d341185ab5"}, + {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599b04b04eb1b5b964a35986bea2bc4381145836fe550cc33c40a796b855b985"}, + {file = "bitarray-2.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9014660472f2080d550157164cc5f9376245a34a0ab877b82b95c1f894af5b28"}, + {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:532d63c54159f7e0fb520e2f72ef596493bc43810eaa75fac7a188e898ab593b"}, + {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1563f11dd70cb1684cfe841e4cf7f35d4f65769de21d12b72cf773a7932615"}, + {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e456150af62ee1f24a0c9976947629bfb80d80b4fbd37aa901cf794db6ba9b0"}, + {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc29909e4cef05d5e49f5d77ace1dc49311c7791734a048b690521c76b4b7a0"}, + {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:608385f07a4b0391d4982d1efb83ad70920cd8ca495a7868e44d2a4511cbf84e"}, + {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2baf7ec353fa64917045b3efe26e7c12ce0d7b4d120c3773a612dce54f91585"}, + {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2c39d1cb04fc277701de6fe2119cc71facc4aff2ca0414b2e326aec337fa1ab4"}, + {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:3caf4ca668854bb23db4b65af0068238677b5791bcc45694bf8990f3e26e85c9"}, + {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4bbfe4474d3470c724e283bd1fe8ee9ab3cb6a4c378112926f45d41e326a7622"}, + {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb941981676dc7859d53199a10a33ca56a3146cce6a45bc6ad70572c1147157d"}, + {file = "bitarray-2.8.2-cp311-cp311-win32.whl", hash = "sha256:e8963d7ac292f41654fa7cbc1a34efdb09e5a42399b2e3689c3fd5b8b4e0fe16"}, + {file = "bitarray-2.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:ee779a291330287b341044635fce2979176d113b0dcce0308dc5d62da7951eec"}, + {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:05d84765bbfd0aa10890c765c56c917c237987325c4e327f3c0febbfc34365c8"}, + {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c7b7be4bff27d7ce6a81d3993755371b5f5b42436afa151868e8fd599acbab19"}, + {file = "bitarray-2.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c3d51ab9f3d5b9a10295abe480c50bf74ee5bf3d984c4cee77e493e575acc869"}, + {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00bad63ef6f9d22ba36b01b89167176a451ea22a916d1dfa77d73e0298f1d1f9"}, + {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:225e19d37b234d4d721557434b7d5590cd63b6342492b689e2d694d44d7cc537"}, + {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e3ab9870c496e5a058436bf4d96ed111ca6154c8ef8147b70c44c188d6fb2c"}, + {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3e182c766cd6f302e99e0d8e44927d533356e9d6ac93fcd09987ebead467aa"}, + {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7bb559b68eb9cb3c4f867eb9fb39a696c4da70a41fad37b410bd0c7b426a8ce"}, + {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:97e658a3793478d6bca684f47f29f62542312683687bc045dc3cb588160e74b3"}, + {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:dd351b8fbc77c2e2ebc3eeadc0cf72bd5024a43bef5a847697e2b076d1201636"}, + {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:280809e56a7098f48165ce134222098e4cfe7084b10d69bbc31367942e541dfd"}, + {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14bc38ced7edffff25ee748c1eabc530624c9af68f86322b030b11b7918b966f"}, + {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de4953b6b1e19dabd23767bd1f83f1cf73978372189dec0e2dd8b3d6971100d6"}, + {file = "bitarray-2.8.2-cp312-cp312-win32.whl", hash = "sha256:99196b4730d887a4bc578f05039b55dc57b131c81b5a5e03efa619b587bdf293"}, + {file = "bitarray-2.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:215a5bf8fdcbed700cc8782d4044e1f036606d5c321710d83e8da6d0fdfe07d5"}, + {file = "bitarray-2.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9c54136c9fab2cefe9801e336b8a3aa7299bcfe7f387379cc6394ad1d5a484b"}, + {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08ad70c1555d9622cecd8f1b132a5341d183a9161aba93cc9739bbaabe4220b0"}, + {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:384be6b7df8fb6a93ddd88d4184094f2ba4f1d07c30dcd4ae164d185d31a2af6"}, + {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd2a098250c683d248a6490ac437ed56f7164d2151572231bd26c76bfe111b11"}, + {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ae5c18b9a70cb0ae576a8a3c8a9a0659356c016b49cc6b263dd987d344f30d"}, + {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:188f5780f1cfbeba0c3ddb1aa3fa0415ab1a8aa04e9e89f70ad5403197013437"}, + {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5f2a96c5b40727bc21a695d3a106f49e88572fa11427bf2193cabd99e624c901"}, + {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b6df948da34b5fb949698092573d798c76c54f2f2188db59276d599075f9ed04"}, + {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f00c328b8dae1828844bac019dfe425d10a2043cc70e2f967224c5392d19ad"}, + {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7965108069f9731306a882872c23ad4f5a8531668e82b27932a19814c52a8dd8"}, + {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:420aa610fe392c4ee700e474673276bb4f3c4f091d001f58b1f018bf650840c1"}, + {file = "bitarray-2.8.2-cp36-cp36m-win32.whl", hash = "sha256:b85929db81105c06e8292c05cac093068e86464555c628c03f99c9f8090d68d4"}, + {file = "bitarray-2.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:cba09dfd3aea2addc994eb21a861c3cea2d68141bb7ebe68b0e94c73405540f9"}, + {file = "bitarray-2.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:172169099797f1ec469b0aadb00c653193a74757f99312c9c17dc1a18d23d972"}, + {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a4fed240728dcc96966e0c4cfd3dce870525377a1cb5afac8e5cfe116ff7b"}, + {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff31bef13fd278446b6d1969a46db9f02c36fd905f3e75878f0fe17271f7d897"}, + {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb8b727cd9ddff848c5f73e65470abb110f026beab403bcebbd74e7439b9bd8f"}, + {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1356c86eefbde3fe8a3c39fb81bbc8b16acc8e442e191408042e8b1d6904e3"}, + {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7706336bd15acf4e42300579e42bef742c01a4eb202998f6c20c443a2ce5fd60"}, + {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a4b43949477dc2b0d3e1d8b7c413ed74f515cef01954cdcc3fb1e2dcc49f2aff"}, + {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:06d9de5db244c6e45a5318713367765de0a57d82ad616869a004a710a95541e9"}, + {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5569c8314335e92570c471d60b4b03eb2a4467864805a560d133d24b27b3961a"}, + {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:76a4faef4c31953aa7b9ebe00d162f7ce9bc03fc8d423ab2dc690a11d7520a8e"}, + {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1474db8c4297026e1daa1699e70e25e56dff91104fe025b1a9804332f2737604"}, + {file = "bitarray-2.8.2-cp37-cp37m-win32.whl", hash = "sha256:85b504f233f0484e9a74df4f286a9ae56fbbe2a648c45726761cf7b6f072cdc8"}, + {file = "bitarray-2.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3dde123ce85d1ba99d9bdf44b1b3174fa22bc8fb10004e0d72bb661a0444c1a9"}, + {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23fae6a5a1403d16592b8823d5dea93f738c6e217a1e1bb0eefad242fb03d47f"}, + {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c44b3022115eb1697315bc51aeadbade1a19d7188bcda66c52d91209cf2963ca"}, + {file = "bitarray-2.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fea9354b7169810e2bdd6f3265ff128b564a25d38479b9ad0a9c5776e4fd0cfc"}, + {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f699bf2cb223aeec04a106003bd2bf8a4fc6d4c5eddf79cacecb6b267657ac5"}, + {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:462c9425fbc5315cbc20a72ca62558e5545bb0f6dc9355e2fa96fa747e9b1a80"}, + {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c8716b4c45fb128cd4da143749e276f150ecb0acb711f4969d7e7ebc9b2a675"}, + {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79fde5b27e35aedd958f5fb58ebabce47d7eddae5a5e3774088c30c9610195ef"}, + {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abf2593b91e36f1cb1c40ac895993c7d2eb30d3f1cb0954a80e5f13697b6b69"}, + {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ab2e03dd140ab93b91f94a785d1cd6082d5ab53ab6ec958726efa0ad17f7b87a"}, + {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9e895cc3e5ffee269dd9866097e227a68022ef2b78d627a6ed737534d0c88c14"}, + {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0bbeb7120ec1a9b26ce423e74cad7b414cea9e35f8e05599e3b3dceb87f4d1b6"}, + {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51d45d56be14b69720d11a8c61e101d86a65dc8a3a9f356bbe4d98cf4f3c5617"}, + {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:726a598e34657772e5f131115741ea8709e9b55fa35d63c4717bc16b2a737d38"}, + {file = "bitarray-2.8.2-cp38-cp38-win32.whl", hash = "sha256:ab87c4c50d65932788d058adbbd28a209144523ffacbab81dd41582ffce26af9"}, + {file = "bitarray-2.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:316147fb62c810a7667277e5ae7bb75b2871c32d2c398aeb4503cbd4cf3315e7"}, + {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36bdde1aba78e4a3a6ce5cbebd0a6bc967b0c3fbd8bd99a197dcc17d654f423c"}, + {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:932f7b77750dff7140522dc97dfd94533a599ef1c5d0be3733f556fd44a68821"}, + {file = "bitarray-2.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5819b95d0ccce864066f062d2329363ae8a64b9c3d076d039c75ffc9204c2a12"}, + {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c28b52e59a5e6aa00a929b35b04473bd479a74237ab1170c573c49e8aca61fe"}, + {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ecdd528268478efeb78ed0132b01104bda6cd8f10c8a57708fc87b1add77e4d"}, + {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f6f245d4a5e707d48274f38551b654a36db4fb83437c98be00d2019263aa364"}, + {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b088f06d9e2f523683ae363e227173ac454dbb56c938c6d42791fdd78bad8da7"}, + {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e883919cea8e446c5c49717a7ce5c93a016a02b9429b81d64b9ab1d80fc12e42"}, + {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:09d729420b8edc4d8a23a518ae4553074a0054d0441c1a461b425c2f033fab5e"}, + {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d0d0923087fe1f2d85daa68463d221e90b4b8ed0356480c887eea90b2a2cc7ee"}, + {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:70cebcf9bc345ac1e034fa781eac3619323eaf87f7bbe26f0e28850beb6f5634"}, + {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:890355bf6ba3dc04b5a23d1328eb1f6062165e6262197cebc9acfebdcb23144c"}, + {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f0b54b95e39036c116ffc057b3f56f6084ce88822de3d5d1f57fa38554ccf5c1"}, + {file = "bitarray-2.8.2-cp39-cp39-win32.whl", hash = "sha256:b499d93fa31a73e31ee62f2cbe07e4df833fd7151734b8f07c48ffe3e4547ec5"}, + {file = "bitarray-2.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:b007aaf5810c708c5a2778e371aa546d7084e4e9f82f65865b2ce5a182376f42"}, + {file = "bitarray-2.8.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b734b074a09b1b2e1de7df423565412d9213faefa8ca422f32be756b189f729"}, + {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd074b06be9484040acb4c2c0462c4d19a43e377716be7ba10440f51a57bb98c"}, + {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678696bb613f0344b79be385747aae705b327a9a32ace45a353dd16497bc719"}, + {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb337ffa10824fa2025c4b1c06a2d809dbed4a4bf9e3ffb262676d084c4e0c50"}, + {file = "bitarray-2.8.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2b3c7aa2c9a6533dc7234d2a303efdcb9df3f4ac4d0919ec1caf568868f12a0a"}, + {file = "bitarray-2.8.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6765c47b487341837b3731cca3c8033b971ee082f6ab41cb430aa3447686eec"}, + {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8566b535bc4ebb26247d6f636a27bb0038bc93fa7e55121628f5cd6b0906ac"}, + {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56764825f64ab983d32b8c1d4ee483f415f2559e59388ba266a9fcafc44305bf"}, + {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f45f7d58c399e90ee3bddff4f3e2f53ff95c948b2d43de304266153ebd1d778"}, + {file = "bitarray-2.8.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:095851409e0db75b1416c8c3e24957135d5a2a206790578e43739e92a00c17c4"}, + {file = "bitarray-2.8.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8bb60d5a948f00901da1d7e4953189259b3c7ef79391fecd6f18db3f48a036fe"}, + {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2dc483ada55ef35990b67dc0e7a779f0b2ce79d156e452dc8b835b03c0dca9"}, + {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a35e308c23f039064600108fc1c8416bd102bc3cf3a6915761a9f7c801237e0"}, + {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa49f6cfcae4305d8cff028dc9c9a881189a38f7ca43c085aef894c58cb6fbde"}, + {file = "bitarray-2.8.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:111bf9913ebee4630e2cb43b61d0abb39813b231262b114e5268cd6a405a22b9"}, + {file = "bitarray-2.8.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b71d82e3f001bcb53463023f7f37e223fff56cf048f577c6d85597db94770f10"}, + {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:440c537fdf2eaee7fdd41fb1dce5701c490c1964fdb74225b10b49a7c45bc7b4"}, + {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c384c49ce52b82d5b0355000b8aeb7e3a7654997916c1e6fd9d29697edda1076"}, + {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27428d7b0e706307d0c697f81599e7af4f52e5873ea6bc269eae3604b16b81fe"}, + {file = "bitarray-2.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4963982d5da0825768f9a80760a8560c3e4cf711a9a7ea06ff9bcb7bd250b131"}, + {file = "bitarray-2.8.2.tar.gz", hash = "sha256:f90b2f44b5b23364d5fbade2c34652e15b1fcfe813c46f828e008f68a709160f"}, ] [[package]] @@ -463,86 +494,74 @@ test = ["pytest", "pytest-cov"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] @@ -550,97 +569,112 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.3.1" +version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8" files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] @@ -710,30 +744,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.1" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699"}, - {file = "cryptography-41.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b"}, - {file = "cryptography-41.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3"}, - {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db"}, - {file = "cryptography-41.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31"}, - {file = "cryptography-41.0.1-cp37-abi3-win32.whl", hash = "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5"}, - {file = "cryptography-41.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039"}, - {file = "cryptography-41.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a"}, - {file = "cryptography-41.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5"}, - {file = "cryptography-41.0.1.tar.gz", hash = "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -751,109 +789,104 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "cytoolz" -version = "0.12.1" +version = "0.12.2" description = "Cython implementation of Toolz: High performance functional utilities" optional = false python-versions = ">=3.6" files = [ - {file = "cytoolz-0.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c59bb4ca88e1c69931468bf21f91c8f64d8bf1999eb163b7a2df336f60c304a"}, - {file = "cytoolz-0.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d700e011156ff112966c6d77faaae125fcaf538f4cec2b9ce8957de82858f0f"}, - {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3f57c48eb939d2986eba4aeaeedf930ebf94d58c91a42d4e0fc45ed5427dc"}, - {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25ff13c468c06da9ef26651dc389e7e8bb7af548f8c1dfb96305f57f18d398a8"}, - {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a734511144309ea6e105406633affb74e303a3df07d8a3954f9b01946e27ecb1"}, - {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48bc2f30d1b2646d675bb8e7778ab59379bf9edc59fe06fb0e7f85ba1271bf44"}, - {file = "cytoolz-0.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30936ae8fa68b6a1ac8ad6c4bacb5a8a00d51bc6c89f9614a1557b0105d09f8a"}, - {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efd1b2da3ee577fcfa723a214f73186aef9674dd5b28242d90443c7a82722b0f"}, - {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6805b007af3557ee6c20dab491b6e55a8177f5b6845d9e6c653374d540366ba7"}, - {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a6e63fc67b23830947b51e0a488992e3c904fce825ead565f3904dcf621d05f7"}, - {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9e324a94856d88ecf10f34c102d0ded67d7c3cf644153d77e34a29720ce6aa47"}, - {file = "cytoolz-0.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02975e2b1e61e47e9afa311f4c1783d155136fad37c54a1cebfe991c5a0798a1"}, - {file = "cytoolz-0.12.1-cp310-cp310-win32.whl", hash = "sha256:b6569f6038133909cd658dbdcc6fc955f791dc47a7f5b55d2066f742253dcbfe"}, - {file = "cytoolz-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:1be368623e46ad3c1ce807e7a436acb119c26001507b31f92ceb21b86e08c386"}, - {file = "cytoolz-0.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:849f461bffa1e7700ccfcb5186df29cd4cdcc9efdb7199cb8b5681dc37045d72"}, - {file = "cytoolz-0.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4284120c978fb7039901bf6e66832cb3e82ac1b2a107512e735bdb04fd5533ed"}, - {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ec296f01c29c809698eaf677211b6255691295c2b35caab2131e1e7eaadfbac"}, - {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37c53f456a1c84566a7d911eec57c4c6280b915ab0600e7671582793cc2769fe"}, - {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b6761791973b1e839b8309d5853b40eeb413368e31beaf5f2b6ed44c6fc7cf0"}, - {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff478682e8ee6dbaa37201bb71bf4a6eee744006ab000e8f5cea05066fc7c845"}, - {file = "cytoolz-0.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:867bebe6be30ee36a836f9b835790762a74f46be8cc339ea57b68dcecdbc1133"}, - {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7e903df991f0957e2b271a37bb25d28e0d260c52825ae67507d15ca55a935961"}, - {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e797c4afb1b7962d3205b1959e1051f7e6bfbba29da44042a9efc2391f1feb38"}, - {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b8eceaa12b7f152b046b67cb053ec2b5b00f73593983de69bc5e63a8aca4a7a8"}, - {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b575393dd431b8e211de35bd593d831dac870172b16e2b7934f3566b8fc89377"}, - {file = "cytoolz-0.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3032c0ba42dee5836d6b57a72a569b65df2c29e8ed266cb900d569003cf933a9"}, - {file = "cytoolz-0.12.1-cp311-cp311-win32.whl", hash = "sha256:c576bd63495150385b8d05eaae775387f378be2fd9805d3ffb4d17c87271fbad"}, - {file = "cytoolz-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:421b224dc4157a0d66625acb5798cf50858cfa06a5232d39a8bd6cf1fa88aca3"}, - {file = "cytoolz-0.12.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:be5a454a95797343d0fb1ed02caecae73a023b1393c112951c84f17ec9f4076c"}, - {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061387aa39b9c1576c25d0c59142513c09e77a2a07bd5d6211a43c7a758b6f45"}, - {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f4dbc3f0ec8f6fc68865489af21dcf042ff007d2737c27bfd73296f15db544"}, - {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a816bff6bf424753e1ac2441902ceaf37ae6718b745a53f6aa1a60c617fb4f5f"}, - {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633f19d1990b1cf9c67dce9c28bf8b5a18e42785d15548607a100e1236384d5d"}, - {file = "cytoolz-0.12.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fa7009c843667868aa8bdb3d68e5ef3d6356dd418b17ed5ca4e1340e82483a5"}, - {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1c29dd04e282ddfd45b457e3551075beec9128aa9271245e58ce924bf6e055f8"}, - {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd35c0be4c46274129dd1678bb911dd4e93d23968b26f4e39cd55bc7cb3b1bac"}, - {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5158ae6d8dd112d003f677039a3613ca7d2592bfe35d7accf23684edb961fc26"}, - {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7eb9e6fa8a82c3d2f519f7d3942898a97792e3895569e9501b9431048289b82f"}, - {file = "cytoolz-0.12.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ac6784cc43aec51a86cf9058a2a343084f8cf46a9281bea5762bfa608127c53b"}, - {file = "cytoolz-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:794cce219bbcb2f36ca220f27d5afd64eaa854e04901bd6f240be156a578b607"}, - {file = "cytoolz-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:695dd8231e4f1bfb9a2363775a6e4e56ad9d2058058f817203a49614f4bfe33b"}, - {file = "cytoolz-0.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1bd8017ef0da935a20106272c5f5ff6b1114add1ccb09cfed1ff7ec5cc01c6d"}, - {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e1ebf6eb4438b8c45cbe7e7b22fc65df0c9efa97a70d3bf2f51e08b19756a5"}, - {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:816c2038008ebf50d81171ddfae377f1af9e71d504ec609469dcb0906bfcf2ae"}, - {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bebe58f7a160db7838eb70990c704db4bdc2d58bd364290fd69be0587be8bac"}, - {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72440305f634604827f96810e4469877b89f5c060d6852267650a49b0e3768c"}, - {file = "cytoolz-0.12.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46ebc463bb45f278a2b94e630061c26e10077cb68d4c93583d8f4199699a5ef"}, - {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e75e287787e6adafed9d8c3d3e7647c0b5eb460221f9f92d7dfe48b45ba77c0d"}, - {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:03ab22c9aeb1535f8647d23b6520b0c3d41aaa18d04ef42b352dde1931f2e2b1"}, - {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b2ac288f27a2689d9e39f4cf4df5437a8eb038eaae515169586c77f9f8fb343a"}, - {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97a24c0d0806fcf9a6e75fc18aeb95adc37eb0baf6451f10a2de23ffd815329d"}, - {file = "cytoolz-0.12.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:42c9e5cd2a48a257b1f2402334b48122501f249b8dcf77082f569f2680f185eb"}, - {file = "cytoolz-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:35fae4eaa0eaf9072a5fe2d244a79e65baae4e5ddbe9cc629c5037af800213a2"}, - {file = "cytoolz-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5af43ca7026ead3dd08b261e4f7163cd2cf3ceaa74fa5a81f7b7ea5d445e41d6"}, - {file = "cytoolz-0.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fcc378fa97f02fbcef090b3611305425d72bd1c0afdd13ef4a82dc67d40638b6"}, - {file = "cytoolz-0.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc3645cf6b9246cb8e179db2803e4f0d148211d2a2cf22d5c9b5219111cd91a0"}, - {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b245b824f4705aef0e4a03fafef3ad6cb59ef43cc564cdbf683ee28dfc11ad5"}, - {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1964dcb5f250fd13fac210944b20810d61ef4094a17fbbe502ab7a7eaeeace7"}, - {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7194a22a4a24f3561cb6ad1cca9c9b2f2cf34cc8d4bce6d6a24c80960323fa8"}, - {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c5434db53f3a94a37ad8aedb231901e001995d899af6ed1165f3d27fa04a6a"}, - {file = "cytoolz-0.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b30cd083ef8af4ba66d9fe5cc75c653ede3f2655f97a032db1a14cc8a006719c"}, - {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bef934bd3e024d512c6c0ad1c66eb173f61d9ccb4dbca8d75f727a5604f7c2f6"}, - {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37320669c364f7d370392af33cc1034b4563da66c22cd3261e3530f4d30dbe4b"}, - {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb95d23defb2322cddf70efb4af6dac191d95edaa343e8c1f58f1afa4f92ecd"}, - {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ac5895d5f78dbd8646fe37266655ba4995f9cfec38a86595282fee69e41787da"}, - {file = "cytoolz-0.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:499af2aff04f65b4c23de1df08e1d1484a93b23ddaaa0163e44b5070b68356eb"}, - {file = "cytoolz-0.12.1-cp38-cp38-win32.whl", hash = "sha256:aa61e3da751a2dfe95aeca603f3ef510071a136ba9905f61ae6cb5d0696271ad"}, - {file = "cytoolz-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5b43ce952a5a31441556c55f5f5f5a8e62c28581a0ff2a2c31c04ef992d73bd"}, - {file = "cytoolz-0.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b8f88251b84b3877254cdd59c86a1dc6b2b39a03c6c9c067d344ef879562e0"}, - {file = "cytoolz-0.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d72415b0110f7958dd3a5ee98a70166f47bd42ede85e3535669c794d06f57406"}, - {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8101ab6de5aa0b26a2b5032bc488d430010c91863e701812d65836b03a12f61"}, - {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2eed428b5e68c28abf2c71195e799850e040d67a27c05f7785319c611665b86a"}, - {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59641eb1f41cb688b3cb2f98c9003c493a5024325f76b5c02333d08dd972127c"}, - {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a48940ff0449ffcf690310bf9228bb57885f7571406ed2fe05c98e299987195"}, - {file = "cytoolz-0.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bae431a5985cdb2014be09d37206c288e0d063940cf9539e9769bd2ec26b220"}, - {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb8b10405960a8e6801a4702af98ea640130ec6ecfc1208195762de3f5503ba9"}, - {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c9a16a5b4f54d5c0a131f56b0ca65998a9a74958b5b36840c280edba4f8b907"}, - {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49911cb533c96d275e31e7eaeb0742ac3f7afe386a1d8c40937814d75039a0f7"}, - {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dbae37d48ef5a0ab90cfaf2b9312d96f034b1c828208a9cbe25377a1b19ba129"}, - {file = "cytoolz-0.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c34e69be4429633fc614febe3127fa03aa418a1abb9252f29d9ba5b3394573a5"}, - {file = "cytoolz-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0d474dacbafbdbb44c7de986bbf71ff56ae62df0d52ab3b6fa966784dc88737a"}, - {file = "cytoolz-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:3d6d0b0075731832343eb88229cea4bf39e96f3fc7acbc449aadbdfec2842703"}, - {file = "cytoolz-0.12.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8506d1863f30d26f577c4ed59d2cfd03d2f39569f9cbaa02a764a9de73d312d5"}, - {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a1eae39656a1685e8b3f433eecfd72015ce5c1d7519e9c8f9402153c68331bb"}, - {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a0055943074c6c85b77fcc3f42f7c54010a3478daa2ed9d6243d0411c84a4d3"}, - {file = "cytoolz-0.12.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a7a325b8fe885a6dd91093616c703134f2dacbd869bc519970df3849c2a15b"}, - {file = "cytoolz-0.12.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7b60caf0fa5f1b49f1062f7dc0f66c7b23e2736bad50fa8296bfb845995e3051"}, - {file = "cytoolz-0.12.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:980e7eb7205e01816a92f3290cfc80507957e64656b9271a0dfebb85fe3718c0"}, - {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d38a40fe153f23cda0e823413fe9d9ebee89dd461827285316eff929fb121e"}, - {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d540e9c34a61b53b6a374ea108794a48388178f7889d772e364cdbd6df37774c"}, - {file = "cytoolz-0.12.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:117871f036926e42d3abcee587eafa9dc7383f1064ac53a806d33e76604de311"}, - {file = "cytoolz-0.12.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:31131b54a0c72efc0eb432dc66df546c6a54f2a7d396c9a34cf65ac1c26b1df8"}, - {file = "cytoolz-0.12.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4534cbfad73cdb1a6dad495530d4186d57d73089c01e9cb0558caab50e46cb3b"}, - {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50db41e875e36aec11881b8b12bc69c6f4836b7dd9e88a9e5bbf26c2cb3ba6cd"}, - {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6716855f9c669c9e25a185d88e0f169839bf8553d16496796325acd114607c11"}, - {file = "cytoolz-0.12.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f32452e833f0605b871626e6c61b71b0cba24233aad0e04accc3240497d4995"}, - {file = "cytoolz-0.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba74c239fc6cb6e962eabc420967c7565f3f363b776c89b3df5234caecf1f463"}, - {file = "cytoolz-0.12.1.tar.gz", hash = "sha256:fc33909397481c90de3cec831bfb88d97e220dc91939d996920202f184b4648e"}, + {file = "cytoolz-0.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bff49986c9bae127928a2f9fd6313146a342bfae8292f63e562f872bd01b871"}, + {file = "cytoolz-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:908c13f305d34322e11b796de358edaeea47dd2d115c33ca22909c5e8fb036fd"}, + {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:735147aa41b8eeb104da186864b55e2a6623c758000081d19c93d759cd9523e3"}, + {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d352d4de060604e605abdc5c8a5d0429d5f156cb9866609065d3003454d4cea"}, + {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89247ac220031a4f9f689688bcee42b38fd770d4cce294e5d914afc53b630abe"}, + {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070ae35c410d644e6df98a8f69f3ed2807e657d0df2a26b2643127cbf6944a5"}, + {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:843500cd3e4884b92fd4037912bc42d5f047108d2c986d36352e880196d465b0"}, + {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a93644d7996fd696ab7f1f466cd75d718d0a00d5c8118b9fe8c64231dc1f85e"}, + {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96796594c770bc6587376e74ddc7d9c982d68f47116bb69d90873db5e0ea88b6"}, + {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:48425107fbb1af3f0f2410c004f16be10ffc9374358e5600b57fa543f46f8def"}, + {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:cde6dbb788a4cbc4a80a72aa96386ba4c2b17bdfff3ace0709799adbe16d6476"}, + {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68ae7091cc73a752f0b938f15bb193de80ca5edf5ae2ea6360d93d3e9228357b"}, + {file = "cytoolz-0.12.2-cp310-cp310-win32.whl", hash = "sha256:997b7e0960072f6bb445402da162f964ea67387b9f18bda2361edcc026e13597"}, + {file = "cytoolz-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:663911786dcde3e4a5d88215c722c531c7548903dc07d418418c0d1c768072c0"}, + {file = "cytoolz-0.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a7d8b869ded171f6cdf584fc2fc6ae03b30a0e1e37a9daf213a59857a62ed90"}, + {file = "cytoolz-0.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b28787eaf2174e68f0acb3c66f9c6b98bdfeb0930c0d0b08e1941c7aedc8d27"}, + {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00547da587f124b32b072ce52dd5e4b37cf199fedcea902e33c67548523e4678"}, + {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:275d53fd769df2102d6c9fc98e553bd8a9a38926f54d6b20cf29f0dd00bf3b75"}, + {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5556acde785a61d4cf8b8534ae109b023cbd2f9df65ee2afbe070be47c410f8c"}, + {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a85b9b9a2530b72b0d3d10e383fc3c2647ae88169d557d5e216f881860318"}, + {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673d6e9e3aa86949343b46ac2b7be266c36e07ce77fa1d40f349e6987a814d6e"}, + {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81e6a9a8fda78a2f4901d2915b25bf620f372997ca1f20a14f7cefef5ad6f6f4"}, + {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa44215bc31675a6380cd896dadb7f2054a7b94cfb87e53e52af844c65406a54"}, + {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a08b4346350660799d81d4016e748bcb134a9083301d41f9618f64a6077f89f2"}, + {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2fb740482794a72e2e5fec58e4d9b00dcd5a60a8cef68431ff12f2ba0e0d9a7e"}, + {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9007bb1290c79402be6b84bcf9e7a622a073859d61fcee146dc7bc47afe328f3"}, + {file = "cytoolz-0.12.2-cp311-cp311-win32.whl", hash = "sha256:a973f5286758f76824ecf19ae1999f6697371a9121c8f163295d181d19a819d7"}, + {file = "cytoolz-0.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:1ce324d1b413636ea5ee929f79637821f13c9e55e9588f38228947294944d2ed"}, + {file = "cytoolz-0.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c08094b9e5d1b6dfb0845a0253cc2655ca64ce70d15162dfdb102e28c8993493"}, + {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf020f4b708f800b353259cd7575e335a79f1ac912d9dda55b2aa0bf3616e42"}, + {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4416ee86a87180b6a28e7483102c92debc077bec59c67eda8cc63fc52a218ac0"}, + {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ee222671eed5c5b16a5ad2aea07f0a715b8b199ee534834bc1dd2798f1ade7"}, + {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad92e37be0b106fdbc575a3a669b43b364a5ef334495c9764de4c2d7541f7a99"}, + {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460c05238fbfe6d848141669d17a751a46c923f9f0c9fd8a3a462ab737623a44"}, + {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9e5075e30be626ef0f9bedf7a15f55ed4d7209e832bc314fdc232dbd61dcbf44"}, + {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:03b58f843f09e73414e82e57f7e8d88f087eaabf8f276b866a40661161da6c51"}, + {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e4e612b7ecc9596e7c859cd9e0cd085e6d0c576b4f0d917299595eb56bf9c05"}, + {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:08a0e03f287e45eb694998bb55ac1643372199c659affa8319dfbbdec7f7fb3c"}, + {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b029bdd5a8b6c9a7c0e8fdbe4fc25ffaa2e09b77f6f3462314696e3a20511829"}, + {file = "cytoolz-0.12.2-cp36-cp36m-win32.whl", hash = "sha256:18580d060fa637ff01541640ecde6de832a248df02b8fb57e6dd578f189d62c7"}, + {file = "cytoolz-0.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:97cf514a9f3426228d8daf880f56488330e4b2948a6d183a106921217850d9eb"}, + {file = "cytoolz-0.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18a0f838677f9510aef0330c0096778dd6406d21d4ff9504bf79d85235a18460"}, + {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb081b2b02bf4405c804de1ece6f904916838ab0e057f1446e4ac12fac827960"}, + {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57233e1600560ceb719bed759dc78393edd541b9a3e7fefc3079abd83c26a6ea"}, + {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0295289c4510efa41174850e75bc9188f82b72b1b54d0ea57d1781729c2924d5"}, + {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a92aab8dd1d427ac9bc7480cfd3481dbab0ef024558f2f5a47de672d8a5ffaa"}, + {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d3495235af09f21aa92a7cdd51504bda640b108b6be834448b774f52852c09"}, + {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9c690b359f503f18bf1c46a6456370e4f6f3fc4320b8774ae69c4f85ecc6c94"}, + {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:481e3129a76ea01adcc0e7097ccb8dbddab1cfc40b6f0e32c670153512957c0f"}, + {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55e94124af9c8fbb1df54195cc092688fdad0765641b738970b6f1d5ea72e776"}, + {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5616d386dfbfba7c39e9418ba668c734f6ceaacc0130877e8a100cad11e6838b"}, + {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:732d08228fa8d366fec284f7032cc868d28a99fa81fc71e3adf7ecedbcf33a0f"}, + {file = "cytoolz-0.12.2-cp37-cp37m-win32.whl", hash = "sha256:f039c5373f7b314b151432c73219216857b19ab9cb834f0eb5d880f74fc7851c"}, + {file = "cytoolz-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:246368e983eaee9851b15d7755f82030eab4aa82098d2a34f6bef9c689d33fcc"}, + {file = "cytoolz-0.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81074edf3c74bc9bd250d223408a5df0ff745d1f7a462597536cd26b9390e2d6"}, + {file = "cytoolz-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:960d85ebaa974ecea4e71fa56d098378fa51fd670ee744614cbb95bf95e28fc7"}, + {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c8d0dff4865da54ae825d43e1721925721b19f3b9aca8e730c2ce73dee2c630"}, + {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9d12436fd64937bd2c9609605f527af7f1a8db6e6637639b44121c0fe715d6"}, + {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd461e402e24929d866f05061d2f8337e3a8456e75e21b72c125abff2477c7f7"}, + {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0568d4da0a9ee9f9f5ab318f6501557f1cfe26d18c96c8e0dac7332ae04c6717"}, + {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:101b5bd32badfc8b1f9c7be04ba3ae04fb47f9c8736590666ce9449bff76e0b1"}, + {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bb624dbaef4661f5e3625c1e39ad98ecceef281d1380e2774d8084ad0810275"}, + {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3e993804e6b04113d61fdb9541b6df2f096ec265a506dad7437517470919c90f"}, + {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ab911033e5937fc221a2c165acce7f66ae5ac9d3e54bec56f3c9c197a96be574"}, + {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6de6a4bdfaee382c2de2a3580b3ae76fce6105da202bbd835e5efbeae6a9c6e"}, + {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9480b4b327be83c4d29cb88bcace761b11f5e30198ffe2287889455c6819e934"}, + {file = "cytoolz-0.12.2-cp38-cp38-win32.whl", hash = "sha256:4180b2785d1278e6abb36a72ac97c92432db53fa2df00ee943d2c15a33627d31"}, + {file = "cytoolz-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:d0086ba8d41d73647b13087a3ca9c020f6bfec338335037e8f5172b4c7c8dce5"}, + {file = "cytoolz-0.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d29988bde28a90a00367edcf92afa1a2f7ecf43ea3ae383291b7da6d380ccc25"}, + {file = "cytoolz-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24c0d71e9ac91f4466b1bd280f7de43aa4d94682daaf34d85d867a9b479b87cc"}, + {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa436abd4ac9ca71859baf5794614e6ec8fa27362f0162baedcc059048da55f7"}, + {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c7b4eac7571707269ebc2893facdf87e359cd5c7cfbfa9e6bd8b33fb1079c5"}, + {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:294d24edc747ef4e1b28e54365f713becb844e7898113fafbe3e9165dc44aeea"}, + {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478051e5ef8278b2429864c8d148efcebdc2be948a61c9a44757cd8c816c98f5"}, + {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14108cafb140dd68fdda610c2bbc6a37bf052cd48cfebf487ed44145f7a2b67f"}, + {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fef7b602ccf8a3c77ab483479ccd7a952a8c5bb1c263156671ba7aaa24d1035"}, + {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9bf51354e15520715f068853e6ab8190e77139940e8b8b633bdb587956a08fb0"}, + {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:388f840fd911d61a96e9e595eaf003f9dc39e847c9060b8e623ab29e556f009b"}, + {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a67f75cc51a2dc7229a8ac84291e4d61dc5abfc8940befcf37a2836d95873340"}, + {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63b31345e20afda2ae30dba246955517a4264464d75e071fc2fa641e88c763ec"}, + {file = "cytoolz-0.12.2-cp39-cp39-win32.whl", hash = "sha256:f6e86ac2b45a95f75c6f744147483e0fc9697ce7dfe1726083324c236f873f8b"}, + {file = "cytoolz-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:5998f81bf6a2b28a802521efe14d9fc119f74b64e87b62ad1b0e7c3d8366d0c7"}, + {file = "cytoolz-0.12.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:593e89e2518eaf81e96edcc9ef2c5fca666e8fc922b03d5cb7a7b8964dbee336"}, + {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff451d614ca1d4227db0ffa627fb51df71968cf0d9baf0210528dad10fdbc3ab"}, + {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9ea4a50d2948738351790047d45f2b1a023facc01bf0361988109b177e8b2f"}, + {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbe038bb78d599b5a29d09c438905defaa615a522bc7e12f8016823179439497"}, + {file = "cytoolz-0.12.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d494befe648c13c98c0f3d56d05489c839c9228a32f58e9777305deb6c2c1cee"}, + {file = "cytoolz-0.12.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c26805b6c8dc8565ed91045c44040bf6c0fe5cb5b390c78cd1d9400d08a6cd39"}, + {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4e32badb2ccf1773e1e74020b7e3b8caf9e92f842c6be7d14888ecdefc2c6c"}, + {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce7889dc3701826d519ede93cdff11940fb5567dbdc165dce0e78047eece02b7"}, + {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c820608e7077416f766b148d75e158e454881961881b657cff808529d261dd24"}, + {file = "cytoolz-0.12.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:698da4fa1f7baeea0607738cb1f9877ed1ba50342b29891b0223221679d6f729"}, + {file = "cytoolz-0.12.2.tar.gz", hash = "sha256:31d4b0455d72d914645f803d917daf4f314d115c70de0578d3820deb8b101f66"}, ] [package.dependencies] @@ -903,78 +936,78 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "dulwich" -version = "0.21.5" +version = "0.21.6" description = "Python Git Library" optional = false python-versions = ">=3.7" files = [ - {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8864719bc176cdd27847332a2059127e2f7bab7db2ff99a999873cb7fff54116"}, - {file = "dulwich-0.21.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3800cdc17d144c1f7e114972293bd6c46688f5bcc2c9228ed0537ded72394082"}, - {file = "dulwich-0.21.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2f676bfed8146966fe934ee734969d7d81548fbd250a8308582973670a9dab1"}, - {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db330fb59fe3b9d253bdf0e49a521739db83689520c4921ab1c5242aaf77b82"}, - {file = "dulwich-0.21.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e8f6d4f4f4d01dd1d3c968e486d4cd77f96f772da7265941bc506de0944ddb9"}, - {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1cc0c9ba19ac1b2372598802bc9201a9c45e5d6f1f7a80ec40deeb10acc4e9ae"}, - {file = "dulwich-0.21.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61e10242b5a7a82faa8996b2c76239cfb633620b02cdd2946e8af6e7eb31d651"}, - {file = "dulwich-0.21.5-cp310-cp310-win32.whl", hash = "sha256:7f357639b56146a396f48e5e0bc9bbaca3d6d51c8340bd825299272b588fff5f"}, - {file = "dulwich-0.21.5-cp310-cp310-win_amd64.whl", hash = "sha256:891d5c73e2b66d05dbb502e44f027dc0dbbd8f6198bc90dae348152e69d0befc"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45d6198e804b539708b73a003419e48fb42ff2c3c6dd93f63f3b134dff6dd259"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c2a565d4e704d7f784cdf9637097141f6d47129c8fffc2fac699d57cb075a169"}, - {file = "dulwich-0.21.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:823091d6b6a1ea07dc4839c9752198fb39193213d103ac189c7669736be2eaff"}, - {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2c9931b657f2206abec0964ec2355ee2c1e04d05f8864e823ffa23c548c4548"}, - {file = "dulwich-0.21.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dc358c2ee727322a09b7c6da43d47a1026049dbd3ad8d612eddca1f9074b298"}, - {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6155ab7388ee01c670f7c5d8003d4e133eebebc7085a856c007989f0ba921b36"}, - {file = "dulwich-0.21.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a605e10d72f90a39ea2e634fbfd80f866fc4df29a02ea6db52ae92e5fd4a2003"}, - {file = "dulwich-0.21.5-cp311-cp311-win32.whl", hash = "sha256:daa607370722c3dce99a0022397c141caefb5ed32032a4f72506f4817ea6405b"}, - {file = "dulwich-0.21.5-cp311-cp311-win_amd64.whl", hash = "sha256:5e56b2c1911c344527edb2bf1a4356e2fb7e086b1ba309666e1e5c2224cdca8a"}, - {file = "dulwich-0.21.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:85d3401d08b1ec78c7d58ae987c4bb7b768a438f3daa74aeb8372bebc7fb16fa"}, - {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90479608e49db93d8c9e4323bc0ec5496678b535446e29d8fd67dc5bbb5d51bf"}, - {file = "dulwich-0.21.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a6bf99f57bcac4c77fc60a58f1b322c91cc4d8c65dc341f76bf402622f89cb"}, - {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3e68b162af2aae995355e7920f89d50d72b53d56021e5ac0a546d493b17cbf7e"}, - {file = "dulwich-0.21.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0ab86d6d42e385bf3438e70f3c9b16de68018bd88929379e3484c0ef7990bd3c"}, - {file = "dulwich-0.21.5-cp37-cp37m-win32.whl", hash = "sha256:f2eeca6d61366cf5ee8aef45bed4245a67d4c0f0d731dc2383eabb80fa695683"}, - {file = "dulwich-0.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:1b20a3656b48c941d49c536824e1e5278a695560e8de1a83b53a630143c4552e"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3932b5e17503b265a85f1eda77ede647681c3bab53bc9572955b6b282abd26ea"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6616132d219234580de88ceb85dd51480dc43b1bdc05887214b8dd9cfd4a9d40"}, - {file = "dulwich-0.21.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaf6c7fb6b13495c19c9aace88821c2ade3c8c55b4e216cd7cc55d3e3807d7fa"}, - {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be12a46f73023970125808a4a78f610c055373096c1ecea3280edee41613eba8"}, - {file = "dulwich-0.21.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baecef0d8b9199822c7912876a03a1af17833f6c0d461efb62decebd45897e49"}, - {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:82f632afb9c7c341a875d46aaa3e6c5e586c7a64ce36c9544fa400f7e4f29754"}, - {file = "dulwich-0.21.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82cdf482f8f51fcc965ffad66180b54a9abaea9b1e985a32e1acbfedf6e0e363"}, - {file = "dulwich-0.21.5-cp38-cp38-win32.whl", hash = "sha256:c8ded43dc0bd2e65420eb01e778034be5ca7f72e397a839167eda7dcb87c4248"}, - {file = "dulwich-0.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:2aba0fdad2a19bd5bb3aad6882580cb33359c67b48412ccd4cfccd932012b35e"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd4ad079758514375f11469e081723ba8831ce4eaa1a64b41f06a3a866d5ac34"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fe62685bf356bfb4d0738f84a3fcf0d1fc9e11fee152e488a20b8c66a52429e"}, - {file = "dulwich-0.21.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aae448da7d80306dda4fc46292fed7efaa466294571ab3448be16714305076f1"}, - {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b24cb1fad0525dba4872e9381bc576ea2a6dcdf06b0ed98f8e953e3b1d719b89"}, - {file = "dulwich-0.21.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e39b7c2c9bda6acae83b25054650a8bb7e373e886e2334721d384e1479bf04b"}, - {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26456dba39d1209fca17187db06967130e27eeecad2b3c2bbbe63467b0bf09d6"}, - {file = "dulwich-0.21.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:281310644e02e3aa6d76bcaffe2063b9031213c4916b5f1a6e68c25bdecfaba4"}, - {file = "dulwich-0.21.5-cp39-cp39-win32.whl", hash = "sha256:4814ca3209dabe0fe7719e9545fbdad7f8bb250c5a225964fe2a31069940c4cf"}, - {file = "dulwich-0.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:c922a4573267486be0ef85216f2da103fb38075b8465dc0e90457843884e4860"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e52b20c4368171b7d32bd3ab0f1d2402e76ad4f2ea915ff9aa73bc9fa2b54d6d"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeb736d777ee21f2117a90fc453ee181aa7eedb9e255b5ef07c51733f3fe5cb6"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8a79c1ed7166f32ad21974fa98d11bf6fd74e94a47e754c777c320e01257c6"}, - {file = "dulwich-0.21.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b943517e30bd651fbc275a892bb96774f3893d95fe5a4dedd84496a98eaaa8ab"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32493a456358a3a6c15bbda07106fc3d4cc50834ee18bc7717968d18be59b223"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa44b812d978fc22a04531f5090c3c369d5facd03fa6e0501d460a661800c7f"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f46bcb6777e5f9f4af24a2bd029e88b77316269d24ce66be590e546a0d8f7b7"}, - {file = "dulwich-0.21.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a917fd3b4493db3716da2260f16f6b18f68d46fbe491d851d154fc0c2d984ae4"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:684c52cff867d10c75a7238151ca307582b3d251bbcd6db9e9cffbc998ef804e"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9019189d7a8f7394df6a22cd5b484238c5776e42282ad5d6d6c626b4c5f43597"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:494024f74c2eef9988adb4352b3651ac1b6c0466176ec62b69d3d3672167ba68"}, - {file = "dulwich-0.21.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f9b6ac1b1c67fc6083c42b7b6cd3b211292c8a6517216c733caf23e8b103ab6d"}, - {file = "dulwich-0.21.5.tar.gz", hash = "sha256:70955e4e249ddda6e34a4636b90f74e931e558f993b17c52570fa6144b993103"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f89bee4c97372e8aaf8ffaf5899f1bcd5184b5306d7eaf68738c1101ceba10e"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:847bb52562a211b596453a602e75739350c86d7edb846b5b1c46896a5c86b9bb"}, + {file = "dulwich-0.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e09d0b4e985b371aa6728773781b19298d361a00772e20f98522868cf7edc6f"}, + {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfb50b3915e223a97f50fbac0dbc298d5fffeaac004eeeb3d552c57fe38416f"}, + {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a64eca1601e79c16df78afe08da9ac9497b934cbc5765990ca7d89a4b87453d9"}, + {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fedd924763a5d640348db43a267a394aa80d551228ad45708e0b0cc2130bb62"}, + {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edc21c3784dd9d9b85abd9fe53f81a884e2cdcc4e5e09ada17287420d64cfd46"}, + {file = "dulwich-0.21.6-cp310-cp310-win32.whl", hash = "sha256:daa3584beabfcf0da76df57535a23c80ff6d8ccde6ddbd23bdc79d317a0e20a7"}, + {file = "dulwich-0.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:40623cc39a3f1634663d22d87f86e2e406cc8ff17ae7a3edc7fcf963c288992f"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ed878553f0b76facbb620b455fafa0943162fe8e386920717781e490444efa"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a89b19f4960e759915dbc23a4dd0abc067b55d8d65e9df50961b73091b87b81a"}, + {file = "dulwich-0.21.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28acbd08d6b38720d99cc01da9dd307a2e0585e00436c95bcac6357b9a9a6f76"}, + {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2f2683e0598f7c7071ef08a0822f062d8744549a0d45f2c156741033b7e3d7d"}, + {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54342cf96fe8a44648505c65f23d18889595762003a168d67d7263df66143bd2"}, + {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a3fc071e5b14f164191286f7ffc02f60fe8b439d01fad0832697cc08c2237dd"}, + {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32d7acfe3fe2ce4502446d8f7a5ab34cfd24c9ff8961e60337638410906a8fbb"}, + {file = "dulwich-0.21.6-cp311-cp311-win32.whl", hash = "sha256:5e58171a5d70f7910f73d25ff82a058edff09a4c1c3bd1de0dc6b1fbc9a42c3e"}, + {file = "dulwich-0.21.6-cp311-cp311-win_amd64.whl", hash = "sha256:ceabe8f96edfb9183034a860f5dc77586700b517457032867b64a03c44e5cf96"}, + {file = "dulwich-0.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4fdc2f081bc3e9e120079c2cea4be213e3f127335aca7c0ab0c19fe791270caa"}, + {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe957564108f74325d0d042d85e0c67ef470921ca92b6e7d330c7c49a3b9c1d"}, + {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2912c8a845c8ccbc79d068a89db7172e355adeb84eb31f062cd3a406d528b30"}, + {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:81e237a6b1b20c79ef62ca19a8fb231f5519bab874b9a1c2acf9c05edcabd600"}, + {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:513d045e74307eeb31592255c38f37042c9aa68ce845a167943018ab5138b0e3"}, + {file = "dulwich-0.21.6-cp37-cp37m-win32.whl", hash = "sha256:e1ac882afa890ef993b8502647e6c6d2b3977ce56e3fe80058ce64607cbc7107"}, + {file = "dulwich-0.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:5d2ccf3d355850674f75655154a6519bf1f1664176c670109fa7041019b286f9"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:28c9724a167c84a83fc6238e0781f4702b5fe8c53ede31604525fb1a9d1833f4"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c816be529680659b6a19798287b4ec6de49040f58160d40b1b2934fd6c28e93f"}, + {file = "dulwich-0.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0545f0fa9444a0eb84977d08e302e3f55fd7c34a0466ec28bedc3c839b2fc1f"}, + {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b1682e8e826471ea3c22b8521435e93799e3db8ad05dd3c8f9b1aaacfa78147"}, + {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ad45928a65f39ea0f451f9989b7aaedba9893d48c3189b544a70c6a1043f71"}, + {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1c9e55233f19cd19c484f607cd90ab578ac50ebfef607f77e3b35c2b6049470"}, + {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:18697b58e0fc5972de68b529b08ac9ddda3f39af27bcf3f6999635ed3da7ef68"}, + {file = "dulwich-0.21.6-cp38-cp38-win32.whl", hash = "sha256:22798e9ba59e32b8faff5d9067e2b5a308f6b0fba9b1e1e928571ad278e7b36c"}, + {file = "dulwich-0.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:6c91e1ed20d3d9a6aaaed9e75adae37272b3fcbcc72bab1eb09574806da88563"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b84450766a3b151c3676fec3e3ed76304e52a84d5d69ade0f34fff2782c1b41"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3da632648ee27b64bb5b285a3a94fddf297a596891cca12ac0df43c4f59448f"}, + {file = "dulwich-0.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cef50c0a19f322b7150248b8fa0862ce1652dec657e340c4020573721e85f215"}, + {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ac20dfcfd6057efb8499158d23f2c059f933aefa381e192100e6d8bc25d562"}, + {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d10aa50c0a9a6dd495990c639358e3a3bbff39e17ff302179be6e93b573da7"}, + {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9b52a08d49731375662936d05a12c4a64a6fe0ce257111f62638e475fb5d26d"}, + {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed2f1f638b9adfba862719693b371ffe5d58e94d552ace9a23dea0fb0db6f468"}, + {file = "dulwich-0.21.6-cp39-cp39-win32.whl", hash = "sha256:bf90f2f9328a82778cf85ab696e4a7926918c3f315c75fc432ba31346bfa89b7"}, + {file = "dulwich-0.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e0dee3840c3c72e1d60c8f87a7a715d8eac023b9e1b80199d97790f7a1c60d9c"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32d3a35caad6879d04711b358b861142440a543f5f4e02df67b13cbcd57f84a6"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04df87098053b7767b46fc04b7943d75443f91c73560ca50157cdc22e27a5d3"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07f145c7b0d82a9f77d157f493a61900e913d1c1f8b1f40d07d919ffb0929a4"}, + {file = "dulwich-0.21.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:008ff08629ab16d3638a9f36cfc6f5bd74b4d594657f2dc1583d8d3201794571"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf469cd5076623c2aad69d01ce9d5392fcb38a5faef91abe1501be733453e37d"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6592ef2d16ac61a27022647cf64a048f5be6e0a6ab2ebc7322bfbe24fb2b971b"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99577b2b37f64bc87280079245fb2963494c345d7db355173ecec7ab3d64b949"}, + {file = "dulwich-0.21.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d7cd9fb896c65e4c28cb9332f2be192817805978dd8dc299681c4fe83c631158"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9002094198e57e88fe77412d3aa64dd05978046ae725a16123ba621a7704628"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b6f8a16f32190aa88c37ef013858b3e01964774bc983900bd0d74ecb6576e6"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee8aba4dec4d0a52737a8a141f3456229c87dcfd7961f8115786a27b6ebefed"}, + {file = "dulwich-0.21.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a780e2a0ff208c4f218e72eff8d13f9aff485ff9a6f3066c22abe4ec8cec7dcd"}, + {file = "dulwich-0.21.6.tar.gz", hash = "sha256:30fbe87e8b51f3813c131e2841c86d007434d160bd16db586b40d47f31dd05b0"}, ] [package.dependencies] @@ -1015,13 +1048,13 @@ test = ["hypothesis (>=6.70.0,<7)", "pytest (>=6.0,<8)", "pytest-cov", "pytest-x [[package]] name = "eth-abi" -version = "4.1.0" +version = "4.2.1" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" optional = false python-versions = ">=3.7.2, <4" files = [ - {file = "eth_abi-4.1.0-py3-none-any.whl", hash = "sha256:15f9870ca054c09a8e474d2d7e81aff0c32421aebdac896193183fc143e31b50"}, - {file = "eth_abi-4.1.0.tar.gz", hash = "sha256:fe738cdb24983adfe89abf727c723c288f8d0029e97fb08160b20bb5290ab475"}, + {file = "eth_abi-4.2.1-py3-none-any.whl", hash = "sha256:abd83410a5326145bf178675c276de0ed154f6dc695dcad1beafaa44d97f44ae"}, + {file = "eth_abi-4.2.1.tar.gz", hash = "sha256:60d88788d53725794cdb07c0f0bb0df2a31a6e1ad19644313fe6117ac24eeeb0"}, ] [package.dependencies] @@ -1065,13 +1098,13 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-x [[package]] name = "eth-ape" -version = "0.6.18" +version = "0.6.22" description = "Ape Ethereum Framework" optional = false python-versions = ">=3.8,<4" files = [ - {file = "eth-ape-0.6.18.tar.gz", hash = "sha256:ebc04365d830ab06a1595a22e8f9c263769a57a9eb1dfc7f4bec2db353ccbf0a"}, - {file = "eth_ape-0.6.18-py3-none-any.whl", hash = "sha256:73e7533c64e23eaba33ecb48b3fbd9e423ffd088050cacb9c551fe66c1ceccf4"}, + {file = "eth-ape-0.6.22.tar.gz", hash = "sha256:dcadd800a532cf253111340c4bd027624fde85755993d147d0233b137a0ad99f"}, + {file = "eth_ape-0.6.22-py3-none-any.whl", hash = "sha256:a68d9ccb0acd533662625da0247854f64e8fd6cd031f83a64b1b96a54a990881"}, ] [package.dependencies] @@ -1081,8 +1114,8 @@ eth-abi = ">=4.1.0,<5" eth-account = ">=0.8,<0.9" eth-typing = ">=3.4,<4" eth-utils = ">=2.2.0,<3" -ethpm-types = ">=0.5.3,<0.6" -evm-trace = ">=0.1.0a22" +ethpm-types = ">=0.5.8,<0.6" +evm-trace = ">=0.1.0a23" hexbytes = ">=0.2.3,<1" ijson = ">=3.1.4,<4" importlib-metadata = "*" @@ -1090,9 +1123,9 @@ ipython = ">=8.5.0,<9" lazyasd = ">=0.1.4" packaging = ">=23.0,<24" pandas = ">=1.3.0,<2" -pluggy = ">=0.12,<2" +pluggy = ">=1.3,<2" py-geth = ">=3.13.0,<4" -pydantic = ">=1.10.8,<2" +pydantic = ">=1.10.8,<3" PyGithub = ">=1.59,<2" pytest = ">=6.0,<8.0" python-dateutil = ">=2.8.2,<3" @@ -1102,13 +1135,14 @@ rich = ">=12.5.1,<13" SQLAlchemy = ">=1.4.35" tqdm = ">=4.62.3,<5.0" traitlets = ">=5.3.0" +urllib3 = ">=1.26.16,<2" watchdog = ">=3.0,<4" web3 = {version = ">=6.7.0,<7", extras = ["tester"]} [package.extras] -dev = ["Sphinx (>=6.1.3,<7)", "black (>=23.7.0,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.4.1,<2)", "myst-parser (>=1.0.0,<2)", "pandas-stubs (==1.2.0.62)", "pre-commit", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine (==3.8.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools", "wheel"] +dev = ["Sphinx (>=6.1.3,<7)", "black (>=23.9.1,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.5.1,<2)", "myst-parser (>=1.0.0,<2)", "pandas-stubs (==1.2.0.62)", "pre-commit", "pydantic (<2.0)", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine (==3.8.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools", "wheel"] doc = ["Sphinx (>=6.1.3,<7)", "myst-parser (>=1.0.0,<2)", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.7.0,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.4.1,<2)", "pandas-stubs (==1.2.0.62)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools"] +lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.5.1,<2)", "pandas-stubs (==1.2.0.62)", "pydantic (<2.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools"] recommended-plugins = ["ape-alchemy", "ape-ens", "ape-etherscan", "ape-foundry", "ape-hardhat", "ape-infura", "ape-solidity", "ape-template", "ape-tokens", "ape-vyper"] release = ["setuptools", "twine (==3.8.0)", "wheel"] test = ["hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-xdist"] @@ -1224,17 +1258,17 @@ test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (= [[package]] name = "eth-stdlib" -version = "0.2.6" +version = "0.2.7" description = "Ethereum Standard Library for Python" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "eth_stdlib-0.2.6-py3-none-any.whl", hash = "sha256:650c7a1e048d1fa6bd2345e5c08ac425ea14f74b11bf4108cee9d16d962d4325"}, - {file = "eth_stdlib-0.2.6.tar.gz", hash = "sha256:08c3508f4e8009ce29566e8b9c4e817418fec6b54eb1ddc5e6fd079337d5fb01"}, + {file = "eth_stdlib-0.2.7-py3-none-any.whl", hash = "sha256:d302d8ca8ca14f6137c01ad4e2124a608e98f9132d51b1b7a32748156018d8af"}, + {file = "eth_stdlib-0.2.7.tar.gz", hash = "sha256:9b9c8d709674d4da34014f2ba1f93fc226d90a15f1cc99da033d481131a8feba"}, ] [package.dependencies] -safe-pysha3 = {version = ">=1.0.3,<2.0.0", markers = "python_version >= \"3.9\""} +pycryptodome = ">=3.18.0,<4.0.0" [package.extras] hypothesis = ["hypothesis (>=6.58.0,<7.0.0)"] @@ -1273,30 +1307,33 @@ test = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "pytest (>=7.0.0)", "pytest-x [[package]] name = "eth-typing" -version = "3.4.0" +version = "3.5.0" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = ">=3.7.2, <4" files = [ - {file = "eth-typing-3.4.0.tar.gz", hash = "sha256:7f49610469811ee97ac43eaf6baa294778ce74042d41e61ecf22e5ebe385590f"}, - {file = "eth_typing-3.4.0-py3-none-any.whl", hash = "sha256:347d50713dd58ab50063b228d8271624ab2de3071bfa32d467b05f0ea31ab4c5"}, + {file = "eth-typing-3.5.0.tar.gz", hash = "sha256:a92f6896896752143a4704c57441eedf7b1f65d5df4b1c20cb802bb4aa602d7e"}, + {file = "eth_typing-3.5.0-py3-none-any.whl", hash = "sha256:a773dbb7d78fcd1539c30264193ca26ec965f3abca2711748e307f117b0a10f5"}, ] +[package.dependencies] +typing-extensions = ">=4.0.1" + [package.extras] dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "2.2.0" +version = "2.2.2" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" optional = false python-versions = ">=3.7,<4" files = [ - {file = "eth-utils-2.2.0.tar.gz", hash = "sha256:7f1a9e10400ee332432a778c321f446abaedb8f538df550e7c9964f446f7e265"}, - {file = "eth_utils-2.2.0-py3-none-any.whl", hash = "sha256:d6e107d522f83adff31237a95bdcc329ac0819a3ac698fe43c8a56fd80813eab"}, + {file = "eth-utils-2.2.2.tar.gz", hash = "sha256:5ca6265177ce544d9d43cdf2272ae2227e5d6d9529c270bbb707d17339087101"}, + {file = "eth_utils-2.2.2-py3-none-any.whl", hash = "sha256:2580a8065273f62ca1ec4c175228c52e626a5f1007e965d2117e5eca1a93cae8"}, ] [package.dependencies] @@ -1307,44 +1344,44 @@ toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} [package.extras] dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==3.8.3)", "hypothesis (>=4.43.0)", "ipython", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] [[package]] name = "ethpm-types" -version = "0.5.4" +version = "0.5.8" description = "ethpm_types: Implementation of EIP-2678" optional = false python-versions = ">=3.8,<4" files = [ - {file = "ethpm-types-0.5.4.tar.gz", hash = "sha256:93e393583bf7271b312430c9ec864bec07a78794d922875d44fe3240bf536f06"}, - {file = "ethpm_types-0.5.4-py3-none-any.whl", hash = "sha256:6d18b5e37f77d6d5326ee6e5d87caecf9ccb5cba2efd978e22dac3a945b7b169"}, + {file = "ethpm-types-0.5.8.tar.gz", hash = "sha256:deaa9ec75cc8d02a047d5a2b065bd038ee6654ba2ffcf6e4c969181eacfdffc2"}, + {file = "ethpm_types-0.5.8-py3-none-any.whl", hash = "sha256:1fcf4fd551133ec917b99406b206713f34112f5e3c030ece5dcb3e5ed8562ee3"}, ] [package.dependencies] eth-utils = ">=2.1.0,<3" hexbytes = ">=0.3.0,<1" py-cid = ">=0.3.0,<0.4" -pydantic = ">=1.10.7,<2" +pydantic = ">=1.10.7,<2.0.dev0 || >=2.3.dev0,<3" requests = ">=2.29.0,<3" [package.extras] -dev = ["IPython", "PyGithub (>=1.54,<2.0)", "black (>=23.3.0,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.0.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<5.11)", "mypy (>=0.991,<1)", "pre-commit", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-requests", "types-setuptools", "wheel"] +dev = ["IPython", "PyGithub (>=1.54,<2.0)", "black (>=23.9.1,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<5.11)", "mypy (>=1.5.1,<2)", "pre-commit", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-requests", "types-setuptools", "wheel"] doc = ["Sphinx (>=4.4.0,<5.0)", "myst-parser (>=0.17.0,<0.18)", "sphinx-click (>=3.1.0,<4.0)", "sphinx-rtd-theme (>=1.0.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.3.0,<24)", "flake8 (>=6.0.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "isort (>=5.10.1,<5.11)", "mypy (>=0.991,<1)", "types-requests", "types-setuptools"] +lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "isort (>=5.10.1,<5.11)", "mypy (>=1.5.1,<2)", "types-requests", "types-setuptools"] release = ["setuptools", "twine", "wheel"] test = ["PyGithub (>=1.54,<2.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "evm-trace" -version = "0.1.0a22" +version = "0.1.0a25" description = "evm-trace: Ethereum Virtual Machine transaction tracing tool" optional = false python-versions = ">=3.8,<4" files = [ - {file = "evm-trace-0.1.0a22.tar.gz", hash = "sha256:5a1bc4ba6024b6311b757a130ee2a59741224bdc05186a8f768798cbcf40d6bc"}, - {file = "evm_trace-0.1.0a22-py3-none-any.whl", hash = "sha256:f4db4ba993289c46d059511f1f4be1cae8452900704d8581b0b83f0bbd9624d1"}, + {file = "evm-trace-0.1.0a25.tar.gz", hash = "sha256:0e5b6d6977bf42c3a5157ee3c5cdc5e57bd23827855283b516fa4e68d09e32e2"}, + {file = "evm_trace-0.1.0a25-py3-none-any.whl", hash = "sha256:5cd30ba28dcb2c7ba2461c124ad9059629c78bd0781f5c3f2a9939427f50cb47"}, ] [package.dependencies] @@ -1352,23 +1389,23 @@ eth-utils = ">=2.1,<3" ethpm-types = ">=0.5.0,<0.6" msgspec = ">=0.8" py-evm = ">=0.7.0a3,<0.8" -pydantic = ">=1.10.1,<2" +pydantic = ">=1.10.1,<3" [package.extras] -dev = ["IPython", "black (>=23.3.0,<24)", "commitizen", "eth-hash[pysha3]", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.2.0,<7.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=0.991,<1)", "pre-commit", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-setuptools", "wheel"] -lint = ["black (>=23.3.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.16)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=0.991,<1)", "types-setuptools"] +dev = ["IPython", "black (>=23.9.1,<24)", "commitizen", "eth-hash[pysha3]", "flake8 (>=6.1.0,<7)", "hypothesis (>=6.2.0,<7.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.5.1,<2)", "pre-commit", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-setuptools", "wheel"] +lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.5.1,<2)", "types-setuptools"] release = ["setuptools", "twine", "wheel"] test = ["eth-hash[pysha3]", "hypothesis (>=6.2.0,<7.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -1376,31 +1413,31 @@ test = ["pytest (>=6)"] [[package]] name = "execnet" -version = "1.9.0" +version = "2.0.2" description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, + {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, + {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, ] [package.extras] -testing = ["pre-commit"] +testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "executing" -version = "1.2.0" +version = "2.0.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = "*" files = [ - {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, - {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, + {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, + {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, ] [package.extras] -tests = ["asttokens", "littleutils", "pytest", "rich"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "fancycompleter" @@ -1419,18 +1456,19 @@ pyrepl = ">=0.8.2" [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.4" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "flake8" @@ -1520,75 +1558,77 @@ files = [ [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.0" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, + {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, + {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, + {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, + {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, + {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, + {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, + {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, + {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, + {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, + {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, + {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, + {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, + {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, + {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, + {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, + {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, + {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, ] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx"] test = ["objgraph", "psutil"] [[package]] @@ -1631,13 +1671,13 @@ lxml = ["lxml"] [[package]] name = "hypothesis" -version = "6.79.3" +version = "6.88.0" description = "A library for property-based testing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "hypothesis-6.79.3-py3-none-any.whl", hash = "sha256:245bed0fcf7612caa0ca1ecaa5c1e3a7100bbf9fd0fe4a24bdd9e46249b2774f"}, - {file = "hypothesis-6.79.3.tar.gz", hash = "sha256:69b55ee1dae2c7edd214e273a977d0dfba542946a211c9ef1f958743b49e430e"}, + {file = "hypothesis-6.88.0-py3-none-any.whl", hash = "sha256:b52b5b5a5065340875fb8a1a45e45391c277d9c5765374560edc1c5e5c3e2d48"}, + {file = "hypothesis-6.88.0.tar.gz", hash = "sha256:c9096ccd5a78bbf75221a2b4a6149e00e254acb17637c94abab98c529b2f61e5"}, ] [package.dependencies] @@ -1646,7 +1686,7 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.16.0)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] @@ -1654,7 +1694,7 @@ django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] ghostwriter = ["black (>=19.10b0)"] lark = ["lark (>=0.10.1)"] -numpy = ["numpy (>=1.16.0)"] +numpy = ["numpy (>=1.17.3)"] pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] @@ -1663,13 +1703,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] [[package]] name = "identify" -version = "2.5.24" +version = "2.5.30" description = "File identification library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, ] [package.extras] @@ -1775,13 +1815,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.7.0" +version = "6.8.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, ] [package.dependencies] @@ -1790,7 +1830,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -1816,13 +1856,13 @@ files = [ [[package]] name = "ipython" -version = "8.14.0" +version = "8.16.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" files = [ - {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, - {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, + {file = "ipython-8.16.1-py3-none-any.whl", hash = "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e"}, + {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"}, ] [package.dependencies] @@ -1830,6 +1870,7 @@ appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} @@ -1840,9 +1881,9 @@ stack-data = "*" traitlets = ">=5" [package.extras] -all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] @@ -1871,40 +1912,40 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jaraco-classes" -version = "3.2.3" +version = "3.3.0" description = "Utility functions for Python class constructs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, - {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, + {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, + {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, ] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "jedi" -version = "0.18.2" +version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, - {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [package.dependencies] -parso = ">=0.8.0,<0.9.0" +parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jeepney" @@ -1923,23 +1964,39 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.17.3" +version = "4.19.1" description = "An implementation of JSON Schema validation for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, - {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, + {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, + {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, ] [package.dependencies] -attrs = ">=17.4.0" -pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +referencing = ">=0.28.0" + [[package]] name = "keyring" version = "23.13.1" @@ -1965,17 +2022,18 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec [[package]] name = "lark" -version = "1.1.5" +version = "1.1.7" description = "a modern parsing library" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "lark-1.1.5-py3-none-any.whl", hash = "sha256:8476f9903e93fbde4f6c327f74d79e9b4bd0ed9294c5dfa3164ab8c581b5de2a"}, - {file = "lark-1.1.5.tar.gz", hash = "sha256:4b534eae1f9af5b4ea000bea95776350befe1981658eea3820a01c37e504bb4d"}, + {file = "lark-1.1.7-py3-none-any.whl", hash = "sha256:9e5dc5bbf93fa1840083707285262514a0ef8a6613874af7ea1cec60468d6e92"}, + {file = "lark-1.1.7.tar.gz", hash = "sha256:be7437bf1f37ab08b355f29ff2571d77d777113d0a8c4352b0c513dced6c5a1e"}, ] [package.extras] atomic-cache = ["atomicwrites"] +interegular = ["interegular (>=0.3.1,<0.4.0)"] nearley = ["js2py"] regex = ["regex"] @@ -2140,13 +2198,13 @@ files = [ [[package]] name = "more-itertools" -version = "9.1.0" +version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, - {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, + {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, + {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] [[package]] @@ -2161,112 +2219,112 @@ files = [ [[package]] name = "msgpack" -version = "1.0.5" +version = "1.0.7" description = "MessagePack serializer" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, + {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, + {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, + {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, + {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, + {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, + {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, + {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, + {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, + {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, + {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, + {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, ] [[package]] name = "msgspec" -version = "0.18.1" +version = "0.18.4" description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." optional = false python-versions = ">=3.8" files = [ - {file = "msgspec-0.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:262e5f1a981644f5e9b28a984d6df238eac0ed2c37d788f40abaf10d380b0424"}, - {file = "msgspec-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3f4a7d5897984f59baf51976682f52f4d2eff88aa64eec8b7f5b80b7a2b6dc5"}, - {file = "msgspec-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d21f0da90d1f3e7f65123d375c2b590bfbe3a014920ea812e1a022027b60d3"}, - {file = "msgspec-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebb71f51ca2f62d0d1cf9585f763e3d9fd8a85a0f00d682112916532964692ae"}, - {file = "msgspec-0.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:52bb422d2b2e80e86d72439edb1a372ff016c0c5d9f44d277b220511486b3f9a"}, - {file = "msgspec-0.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:119ff9d5412eccf53f0d7ab43b587a58f6f806a40ae6b14fd8140173b1cc0285"}, - {file = "msgspec-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:36da63a8f64292fff7c814bd3cbbba669f0fa5068c149b4a386dba662ace5621"}, - {file = "msgspec-0.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b132ae2691623b3fbee730604671da3bf57ca97d648f1a46a35ea09c6f490fcf"}, - {file = "msgspec-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:944af5f9ec66de8e21b30ccafdab3e87be11d757ca9304c01e3b2efc373f3293"}, - {file = "msgspec-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e96b71e98728830ea3d20acf65969ba7059dfc9e32581039f05e88b8461d98b"}, - {file = "msgspec-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:914ab6407fdce1bc795583ba0428f49baf6eedd40df117200ec8fe7666ca2ec4"}, - {file = "msgspec-0.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13980627b9ba28fac2e051757ef5edbe0bebfef411d648e58d2100b86c5eca03"}, - {file = "msgspec-0.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6bd460e2057a6d9ca6d0e2b848a441c6e30977012d1fca43e4bc1702c9abca5"}, - {file = "msgspec-0.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:c63f9dc8c33ca56903d7957ee7a5d0b3ece6b345b8b10f44b226787a4410c8e1"}, - {file = "msgspec-0.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a88d2fb82eb59de0f0f365a27dcea2020440883770b99353e0a3e0aa8ef552a"}, - {file = "msgspec-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f936f6ea351c2e8c0d53875a6a71d9887603e3faadbe380172d22283e011f1b3"}, - {file = "msgspec-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597324c72b504f04fb283078b748e664f7f7fc5337bb493e0996511bab895e7e"}, - {file = "msgspec-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94eca90ac47a24cb07b3702c863a6b0881830ea02de7ac58a00f778b4b7762b"}, - {file = "msgspec-0.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c256b21baa1336d651aa9955d69d3f8d0e971d78e0e75fd28a89742af7dcff65"}, - {file = "msgspec-0.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9721948182d471c4f1655634f1970628a7321ec58a2cfb89d35af78c2fc2ebfa"}, - {file = "msgspec-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:f01fe87b592185a11091206a745fdcba5b40d4c4342d1089959df49f18d6a700"}, - {file = "msgspec-0.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:966ee615b8227c208276863c4269a01dfe8c3b2538b8e1ee33bb01010fe7a3af"}, - {file = "msgspec-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d94555a79013c100b1345e0a5a6d36bfa915e74831e42de3a97cb81313426ea7"}, - {file = "msgspec-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c63a0f49540e2a888acec8c154066b6ef985813ce27132eb38e1f0c7f49df27"}, - {file = "msgspec-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0784a23d1b5b8a6fe4c9f5b9c1779adc5bff4de0fe388545dc2a4f1b5b90e546"}, - {file = "msgspec-0.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4bd273e373e220437a22a5ad83a90e3dd648c4e625f48e42e172026de77a3038"}, - {file = "msgspec-0.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a0f5070d77658ac953f63114fbaafc6ec967e9e61993ff70cc2432e938a3cf3e"}, - {file = "msgspec-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:fe22658000c165be1055c8df3f7ecc3db85be838d47d1ce1e0526b10747bdf0f"}, - {file = "msgspec-0.18.1.tar.gz", hash = "sha256:a7d837e370cfe5afb941e9c922dbdbee9c854b21bafdebaf068bdf15c43ec21d"}, + {file = "msgspec-0.18.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d24a291a3c94a7f5e26e8f5ef93e72bf26c10dfeed4d6ae8fc87ead02f4e265"}, + {file = "msgspec-0.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9714b78965047638c01c818b4b418133d77e849017de17b0655ee37b714b47a6"}, + {file = "msgspec-0.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:241277eed9fd91037372519fca62aecf823f7229c1d351030d0be5e3302580c1"}, + {file = "msgspec-0.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d08175cbb55c1a87dd258645dce6cd00705d6088bf88e7cf510a9d5c24b0720b"}, + {file = "msgspec-0.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:da13a06e77d683204eee3b134b08ecd5e4759a79014027b1bcd7a12c614b466d"}, + {file = "msgspec-0.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73e70217ff5e4ac244c8f1b0769215cbc81e1c904e135597a5b71162857e6c27"}, + {file = "msgspec-0.18.4-cp310-cp310-win_amd64.whl", hash = "sha256:dc25e6100026f5e1ecb5120150f4e78beb909cbeb0eb724b9982361b75c86c6b"}, + {file = "msgspec-0.18.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e14287c3405093645b3812e3436598edd383b9ed724c686852e65d569f39f953"}, + {file = "msgspec-0.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acdcef2fccfff02f80ac8673dbeab205c288b680d81e05bfb5ae0be6b1502a7e"}, + {file = "msgspec-0.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b052fd7d25a8aa2ffde10126ee1d97b4c6f3d81f3f3ab1258ff759a2bd794874"}, + {file = "msgspec-0.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826dcb0dfaac0abbcf3a3ae991749900671796eb688b017a69a82bde1e624662"}, + {file = "msgspec-0.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:86800265f87f192a0daefe668e0a9634c35bf8af94b1f297e1352ac62d2e26da"}, + {file = "msgspec-0.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:227fee75a25080a8b3677cdd95b9c0c3652e27869004a084886c65eb558b3dd6"}, + {file = "msgspec-0.18.4-cp311-cp311-win_amd64.whl", hash = "sha256:828ef92f6654915c36ef6c7d8fec92404a13be48f9ff85f060e73b30299bafe1"}, + {file = "msgspec-0.18.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8476848f4937da8faec53700891694df2e412453cb7445991f0664cdd1e2dd16"}, + {file = "msgspec-0.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f668102958841c5bbd3ba7cf569a65d17aa3bdcf22124f394dfcfcf53cc5a9b9"}, + {file = "msgspec-0.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc2405dba5af6478dedd3512bb92197b6f9d1bc0095655afbe9b54d7a426f19f"}, + {file = "msgspec-0.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99f3c13569a5add0980b0d8c6e0bd94a656f6363b26107435b3091df979d228"}, + {file = "msgspec-0.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a198409f672f93534c9c36bdc9eea9fb536827bd63ea846882365516a961356"}, + {file = "msgspec-0.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e21bc5aae6b80dfe4eb75dc1bb29af65483f967d5522e9e3812115a0ba285cac"}, + {file = "msgspec-0.18.4-cp312-cp312-win_amd64.whl", hash = "sha256:44d551aee1ec8aa2d7b64762557c266bcbf7d5109f2246955718d05becc509d6"}, + {file = "msgspec-0.18.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bbbc08d59f74de5791bda63569f26a35ae1dd6bd20c55c3ceba5567b0e5a8ef1"}, + {file = "msgspec-0.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87bc01949a35970398f5267df8ed4189c340727bb6feec99efdb9969dd05cf30"}, + {file = "msgspec-0.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96ccaef83adc0ce96d95328a03289cd5aead4fe400aac21fbe2008855a124a01"}, + {file = "msgspec-0.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6229dd49438d81ed7a3470e3cbc9646b1cc1b120d415a1786df880dabb1d1c4"}, + {file = "msgspec-0.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:55e578fd921c88de0d3a209fe5fd392bb66623924c6525b42cea37c72bf8d558"}, + {file = "msgspec-0.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e95bd0a946b5b7206f27c0f654f490231c9ad5e5a4ff65af8c986f5114dfaf0e"}, + {file = "msgspec-0.18.4-cp38-cp38-win_amd64.whl", hash = "sha256:7e95817021db96c43fd81244228e185b13b085cca3d5169af4e2dfe3ff412954"}, + {file = "msgspec-0.18.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:847d79f6f0b698671ff390aa5a66e207108f2c23b077ef9314ca4fe7819fa4ec"}, + {file = "msgspec-0.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4294158c233884f3b3220f0e96a30d3e916a4781f9502ae6d477bd57bbc80ad"}, + {file = "msgspec-0.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb11ba2709019192636042df5c8db8738e45946735627021b7e7934714526e4"}, + {file = "msgspec-0.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b01efbf80a987a99e9079257c893c026dc661d4cd05caa1f7eabf4accc7f1fbc"}, + {file = "msgspec-0.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:991aa3c76d1b1ec84e840d0b3c96692af834e1f8a1e1a3974cbd189eaf0f2276"}, + {file = "msgspec-0.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8064908ddb3d95d3261aaca48fd38abb16ccf59dc3f2d01eb4e04591fc1e9bd4"}, + {file = "msgspec-0.18.4-cp39-cp39-win_amd64.whl", hash = "sha256:5f446f16ea57d70cceec29b7cb85ec0b3bea032e3dec316806e38575ea3a69b4"}, + {file = "msgspec-0.18.4.tar.gz", hash = "sha256:cb62030bd6b1a00b01a2fcb09735016011696304e6b1d3321e58022548268d3e"}, ] [package.extras] @@ -2420,13 +2478,13 @@ files = [ [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -2506,13 +2564,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -2576,28 +2634,28 @@ testing = ["pytest", "pytest-cov"] [[package]] name = "platformdirs" -version = "3.8.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, - {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -2657,13 +2715,13 @@ files = [ [[package]] name = "poetry-plugin-export" -version = "1.4.0" +version = "1.5.0" description = "Poetry plugin to export the dependencies to various formats" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8,<4.0" files = [ - {file = "poetry_plugin_export-1.4.0-py3-none-any.whl", hash = "sha256:5d9186d6f77cf2bf35fc96bd11fe650cc7656e515b17d99cb65018d50ba22589"}, - {file = "poetry_plugin_export-1.4.0.tar.gz", hash = "sha256:f16974cd9f222d4ef640fa97a8d661b04d4fb339e51da93973f1bc9d578e183f"}, + {file = "poetry_plugin_export-1.5.0-py3-none-any.whl", hash = "sha256:cd8267597970375ca29868daec5e7718bad500c7584663af3eeb0ed16f24e2bd"}, + {file = "poetry_plugin_export-1.5.0.tar.gz", hash = "sha256:ecc8738da0c81c3758e36b4e72e04ae59648a547492af2ffe6245af3594bb00f"}, ] [package.dependencies] @@ -2672,13 +2730,13 @@ poetry-core = ">=1.6.0,<2.0.0" [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -2690,13 +2748,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.38" +version = "3.0.39" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, - {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, ] [package.dependencies] @@ -2704,24 +2762,24 @@ wcwidth = "*" [[package]] name = "protobuf" -version = "4.24.1" +version = "4.24.4" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "protobuf-4.24.1-cp310-abi3-win32.whl", hash = "sha256:d414199ca605eeb498adc4d2ba82aedc0379dca4a7c364ff9bc9a179aa28e71b"}, - {file = "protobuf-4.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:5906c5e79ff50fe38b2d49d37db5874e3c8010826f2362f79996d83128a8ed9b"}, - {file = "protobuf-4.24.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:970c701ee16788d74f3de20938520d7a0aebc7e4fff37096a48804c80d2908cf"}, - {file = "protobuf-4.24.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc361148e902949dcb953bbcb148c99fe8f8854291ad01107e4120361849fd0e"}, - {file = "protobuf-4.24.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5d32363d14aca6e5c9e9d5918ad8fb65b091b6df66740ae9de50ac3916055e43"}, - {file = "protobuf-4.24.1-cp37-cp37m-win32.whl", hash = "sha256:df015c47d6855b8efa0b9be706c70bf7f050a4d5ac6d37fb043fbd95157a0e25"}, - {file = "protobuf-4.24.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d4af4fd9e9418e819be30f8df2a16e72fbad546a7576ac7f3653be92a6966d30"}, - {file = "protobuf-4.24.1-cp38-cp38-win32.whl", hash = "sha256:302e8752c760549ed4c7a508abc86b25d46553c81989343782809e1a062a2ef9"}, - {file = "protobuf-4.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:06437f0d4bb0d5f29e3d392aba69600188d4be5ad1e0a3370e581a9bf75a3081"}, - {file = "protobuf-4.24.1-cp39-cp39-win32.whl", hash = "sha256:0b2b224e9541fe9f046dd7317d05f08769c332b7e4c54d93c7f0f372dedb0b1a"}, - {file = "protobuf-4.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd39b9094a4cc003a1f911b847ab379f89059f478c0b611ba1215053e295132e"}, - {file = "protobuf-4.24.1-py3-none-any.whl", hash = "sha256:55dd644adc27d2a624339332755fe077c7f26971045b469ebb9732a69ce1f2ca"}, - {file = "protobuf-4.24.1.tar.gz", hash = "sha256:44837a5ed9c9418ad5d502f89f28ba102e9cd172b6668bc813f21716f9273348"}, + {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, + {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, + {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, + {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, + {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, + {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, + {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, + {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, + {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, + {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, + {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, + {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, + {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, ] [[package]] @@ -2926,96 +2984,181 @@ files = [ [[package]] name = "pycryptodome" -version = "3.18.0" +version = "3.19.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf"}, - {file = "pycryptodome-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e"}, - {file = "pycryptodome-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1"}, - {file = "pycryptodome-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf"}, - {file = "pycryptodome-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e"}, - {file = "pycryptodome-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win32.whl", hash = "sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3"}, - {file = "pycryptodome-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec"}, - {file = "pycryptodome-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403"}, - {file = "pycryptodome-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec"}, - {file = "pycryptodome-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380"}, - {file = "pycryptodome-3.18.0.tar.gz", hash = "sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, + {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, + {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, + {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, + {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, + {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, + {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, + {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, + {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, + {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, + {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, + {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, + {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, + {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, ] [[package]] name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" +version = "2.4.2" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.10.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.10.1" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyethash" @@ -3057,13 +3200,13 @@ requests = ">=2.14.0" [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -3149,51 +3292,15 @@ files = [ {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, ] -[[package]] -name = "pyrsistent" -version = "0.19.3" -description = "Persistent/Functional/Immutable data structures" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, - {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, - {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, - {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, - {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, - {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, - {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, - {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, - {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, - {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, - {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, - {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, - {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, - {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, - {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, - {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, - {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, - {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, - {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, - {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, - {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, - {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, - {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, - {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, - {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, - {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, - {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, -] - [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -3224,17 +3331,17 @@ pytest = ">=3.10" [[package]] name = "pytest-repeat" -version = "0.9.1" +version = "0.9.3" description = "pytest plugin for repeating tests" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "pytest-repeat-0.9.1.tar.gz", hash = "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b"}, - {file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"}, + {file = "pytest_repeat-0.9.3-py3-none-any.whl", hash = "sha256:26ab2df18226af9d5ce441c858f273121e92ff55f5bb311d25755b8d7abdd8ed"}, + {file = "pytest_repeat-0.9.3.tar.gz", hash = "sha256:ffd3836dfcd67bb270bec648b330e20be37d2966448c4148c4092d1e8aba8185"}, ] [package.dependencies] -pytest = ">=3.6" +pytest = "*" [[package]] name = "pytest-xdist" @@ -3282,13 +3389,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] @@ -3326,263 +3433,304 @@ files = [ [[package]] name = "pywin32-ctypes" -version = "0.2.1" +version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" files = [ - {file = "pywin32-ctypes-0.2.1.tar.gz", hash = "sha256:934a2def1e5cbc472b2b6bf80680c0f03cd87df65dfd58bfd1846969de095b03"}, - {file = "pywin32_ctypes-0.2.1-py3-none-any.whl", hash = "sha256:b9a53ef754c894a525469933ab2a447c74ec1ea6b9d2ef446f40ec50d3dcec9f"}, + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, ] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "rapidfuzz" -version = "2.15.1" +version = "2.15.2" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.7" files = [ - {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc0bc259ebe3b93e7ce9df50b3d00e7345335d35acbd735163b7c4b1957074d3"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d59fb3a410d253f50099d7063855c2b95df1ef20ad93ea3a6b84115590899f25"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c525a3da17b6d79d61613096c8683da86e3573e807dfaecf422eea09e82b5ba6"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4deae6a918ecc260d0c4612257be8ba321d8e913ccb43155403842758c46fbe"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2577463d10811386e704a3ab58b903eb4e2a31b24dfd9886d789b0084d614b01"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f67d5f56aa48c0da9de4ab81bffb310683cf7815f05ea38e5aa64f3ba4368339"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7927722ff43690e52b3145b5bd3089151d841d350c6f8378c3cfac91f67573a"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6534afc787e32c4104f65cdeb55f6abe4d803a2d0553221d00ef9ce12788dcde"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d0ae6ec79a1931929bb9dd57bc173eb5ba4c7197461bf69e3a34b6dd314feed2"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be7ccc45c4d1a7dfb595f260e8022a90c6cb380c2a346ee5aae93f85c96d362b"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ba013500a2b68c64b2aecc5fb56a2dad6c2872cf545a0308fd044827b6e5f6a"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4d9f7d10065f657f960b48699e7dddfce14ab91af4bab37a215f0722daf0d716"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e24a1b802cea04160b3fccd75d2d0905065783ebc9de157d83c14fb9e1c6ce2"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-win32.whl", hash = "sha256:dffdf03499e0a5b3442951bb82b556333b069e0661e80568752786c79c5b32de"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d150d90a7c6caae7962f29f857a4e61d42038cfd82c9df38508daf30c648ae7"}, - {file = "rapidfuzz-2.15.1-cp310-cp310-win_arm64.whl", hash = "sha256:87c30e9184998ff6eb0fa9221f94282ce7c908fd0da96a1ef66ecadfaaa4cdb7"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6986413cb37035eb796e32f049cbc8c13d8630a4ac1e0484e3e268bb3662bd1b"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a72f26e010d4774b676f36e43c0fc8a2c26659efef4b3be3fd7714d3491e9957"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5cd54c98a387cca111b3b784fc97a4f141244bbc28a92d4bde53f164464112e"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7fac7c3da39f93e6b2ebe386ed0ffe1cefec91509b91857f6e1204509e931f"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f976e76ac72f650790b3a5402431612175b2ac0363179446285cb3c901136ca9"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abde47e1595902a490ed14d4338d21c3509156abb2042a99e6da51f928e0c117"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca8f1747007a3ce919739a60fa95c5325f7667cccf6f1c1ef18ae799af119f5e"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35da09ab9797b020d0d4f07a66871dfc70ea6566363811090353ea971748b5a"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a3a769ca7580686a66046b77df33851b3c2d796dc1eb60c269b68f690f3e1b65"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d50622efefdb03a640a51a6123748cd151d305c1f0431af762e833d6ffef71f0"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b7461b0a7651d68bc23f0896bffceea40f62887e5ab8397bf7caa883592ef5cb"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:074ee9e17912e025c72a5780ee4c7c413ea35cd26449719cc399b852d4e42533"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7025fb105a11f503943f17718cdb8241ea3bb4d812c710c609e69bead40e2ff0"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-win32.whl", hash = "sha256:2084d36b95139413cef25e9487257a1cc892b93bd1481acd2a9656f7a1d9930c"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:5a738fcd24e34bce4b19126b92fdae15482d6d3a90bd687fd3d24ce9d28ce82d"}, - {file = "rapidfuzz-2.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:dc3cafa68cfa54638632bdcadf9aab89a3d182b4a3f04d2cad7585ed58ea8731"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c53d57ba7a88f7bf304d4ea5a14a0ca112db0e0178fff745d9005acf2879f7d"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6ee758eec4cf2215dc8d8eafafcea0d1f48ad4b0135767db1b0f7c5c40a17dd"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d93ba3ae59275e7a3a116dac4ffdb05e9598bf3ee0861fecc5b60fb042d539e"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c3ff75e647908ddbe9aa917fbe39a112d5631171f3fcea5809e2363e525a59d"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d89c421702474c6361245b6b199e6e9783febacdbfb6b002669e6cb3ef17a09"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f69e6199fec0f58f9a89afbbaea78d637c7ce77f656a03a1d6ea6abdc1d44f8"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:41dfea282844d0628279b4db2929da0dacb8ac317ddc5dcccc30093cf16357c1"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2dd03477feefeccda07b7659dd614f6738cfc4f9b6779dd61b262a73b0a9a178"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5efe035aa76ff37d1b5fa661de3c4b4944de9ff227a6c0b2e390a95c101814c0"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ed2cf7c69102c7a0a06926d747ed855bc836f52e8d59a5d1e3adfd980d1bd165"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a0e441d4c2025110ec3eba5d54f11f78183269a10152b3a757a739ffd1bb12bf"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-win32.whl", hash = "sha256:a4a54efe17cc9f53589c748b53f28776dfdfb9bc83619685740cb7c37985ac2f"}, - {file = "rapidfuzz-2.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bb8318116ecac4dfb84841d8b9b461f9bb0c3be5b616418387d104f72d2a16d1"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e9296c530e544f68858c3416ad1d982a1854f71e9d2d3dcedb5b216e6d54f067"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c4bcdb9238f11f8c4eba1b898937f09b92280d6f900023a8216008f299b41a"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb40a279e134bb3fef099a8b58ed5beefb201033d29bdac005bddcdb004ef71"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7381c11cb590bbd4e6f2d8779a0b34fdd2234dfa13d0211f6aee8ca166d9d05"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfdcdedfd12a0077193f2cf3626ff6722c5a184adf0d2d51f1ec984bf21c23c3"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85bece1ec59bda8b982bd719507d468d4df746dfb1988df11d916b5e9fe19e8"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b393f4a1eaa6867ffac6aef58cfb04bab2b3d7d8e40b9fe2cf40dd1d384601"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53de456ef020a77bf9d7c6c54860a48e2e902584d55d3001766140ac45c54bc7"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2492330bc38b76ed967eab7bdaea63a89b6ceb254489e2c65c3824efcbf72993"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:099e4c6befaa8957a816bdb67ce664871f10aaec9bebf2f61368cf7e0869a7a1"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:46599b2ad4045dd3f794a24a6db1e753d23304699d4984462cf1ead02a51ddf3"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:591f19d16758a3c55c9d7a0b786b40d95599a5b244d6eaef79c7a74fcf5104d8"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed17359061840eb249f8d833cb213942e8299ffc4f67251a6ed61833a9f2ea20"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-win32.whl", hash = "sha256:aa1e5aad325168e29bf8e17006479b97024aa9d2fdbe12062bd2f8f09080acf8"}, - {file = "rapidfuzz-2.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:c2bb68832b140c551dbed691290bef4ee6719d4e8ce1b7226a3736f61a9d1a83"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fac40972cf7b6c14dded88ae2331eb50dfbc278aa9195473ef6fc6bfe49f686"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0e456cbdc0abf39352800309dab82fd3251179fa0ff6573fa117f51f4e84be8"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:22b9d22022b9d09fd4ece15102270ab9b6a5cfea8b6f6d1965c1df7e3783f5ff"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46754fe404a9a6f5cbf7abe02d74af390038d94c9b8c923b3f362467606bfa28"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91abb8bf7610efe326394adc1d45e1baca8f360e74187f3fa0ef3df80cdd3ba6"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e40a2f60024f9d3c15401e668f732800114a023f3f8d8c40f1521a62081ff054"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a48ee83916401ac73938526d7bd804e01d2a8fe61809df7f1577b0b3b31049a3"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71580052f9dbac443c02f60484e5a2e5f72ad4351b84b2009fbe345b1f38422"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:82b86d5b8c1b9bcbc65236d75f81023c78d06a721c3e0229889ff4ed5c858169"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fc4528b7736e5c30bc954022c2cf410889abc19504a023abadbc59cdf9f37cae"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e1e0e569108a5760d8f01d0f2148dd08cc9a39ead79fbefefca9e7c7723c7e88"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:94e1c97f0ad45b05003806f8a13efc1fc78983e52fa2ddb00629003acf4676ef"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47e81767a962e41477a85ad7ac937e34d19a7d2a80be65614f008a5ead671c56"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-win32.whl", hash = "sha256:79fc574aaf2d7c27ec1022e29c9c18f83cdaf790c71c05779528901e0caad89b"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:f3dd4bcef2d600e0aa121e19e6e62f6f06f22a89f82ef62755e205ce14727874"}, - {file = "rapidfuzz-2.15.1-cp39-cp39-win_arm64.whl", hash = "sha256:cac095cbdf44bc286339a77214bbca6d4d228c9ebae3da5ff6a80aaeb7c35634"}, - {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b89d1126be65c85763d56e3b47d75f1a9b7c5529857b4d572079b9a636eaa8a7"}, - {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7460e91168229768be882ea365ba0ac7da43e57f9416e2cfadc396a7df3c2"}, - {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c33c03e7092642c38f8a15ca2d8fc38da366f2526ec3b46adf19d5c7aa48ba"}, - {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040faca2e26d9dab5541b45ce72b3f6c0e36786234703fc2ac8c6f53bb576743"}, - {file = "rapidfuzz-2.15.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6e2a3b23e1e9aa13474b3c710bba770d0dcc34d517d3dd6f97435a32873e3f28"}, - {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e597b9dfd6dd180982684840975c458c50d447e46928efe3e0120e4ec6f6686"}, - {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d14752c9dd2036c5f36ebe8db5f027275fa7d6b3ec6484158f83efb674bab84e"}, - {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558224b6fc6124d13fa32d57876f626a7d6188ba2a97cbaea33a6ee38a867e31"}, - {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c89cfa88dc16fd8c9bcc0c7f0b0073f7ef1e27cceb246c9f5a3f7004fa97c4d"}, - {file = "rapidfuzz-2.15.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:509c5b631cd64df69f0f011893983eb15b8be087a55bad72f3d616b6ae6a0f96"}, - {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0f73a04135a03a6e40393ecd5d46a7a1049d353fc5c24b82849830d09817991f"}, - {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99d53138a2dfe8ada67cb2855719f934af2733d726fbf73247844ce4dd6dd5"}, - {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f01fa757f0fb332a1f045168d29b0d005de6c39ee5ce5d6c51f2563bb53c601b"}, - {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60368e1add6e550faae65614844c43f8a96e37bf99404643b648bf2dba92c0fb"}, - {file = "rapidfuzz-2.15.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:785744f1270828cc632c5a3660409dee9bcaac6931a081bae57542c93e4d46c4"}, - {file = "rapidfuzz-2.15.1.tar.gz", hash = "sha256:d62137c2ca37aea90a11003ad7dc109c8f1739bfbe5a9a217f3cdb07d7ac00f6"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b2e64e08588965b2490ee6b581d3901dd207ec3f6919b1c8da495183acfde953"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0af367ecb515ae695d7da21b0bd05784f388621e9d6a2e21dc96e6ba5d18d95f"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:892d0d75f0b820d949b0bf9502f746cfcbaab98d8a47653fa8369607fde250f1"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcf1d564ec948a4bf0750252579871be1790de66200f4cf8d624446017d74ee9"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab2f86733fe34cd825b6cbc688d41b7eb19ae0ce1ea7dc57eac13862d4b9ecb5"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bdc497a8930428fa35158c58a744ddaa930621b80adfb61884456d8f184288a"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97f6c4948ca07ad1a30e70da56ec672422ef6bf18d10b6a881e7a64ba73a126d"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f3e2cc54edffd62ae38a03802b79c0f0cec6c2f89819607350fb5c4c00442d7"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a252ccb39d628d0f68bab80ba18a02e0d1853a0ec71991e665a6bf81a28c79a"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff82edd7ff9796e2ca349aa583fcb6b9ae96db0b6c5a76dcf0c1f67b1cb86964"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0860877f455833e5ed7113e859a9b2bf9670b22fdc7a48b81384a04c4a8e8a48"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:1a78c75ad082fdd58fdcf04551b7737c96aa9e870f1b008b881fc179e7dc6208"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a9df54f67a22a2447b8b6648880de9ede5e2a2e568644e1de770df9bef5c2fb4"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-win32.whl", hash = "sha256:055e85bb1237142da4ed024f9986c3720d484036f8dd550b090582f288b71bb9"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:8f220df380c127ef8a9129d8878dabf99ed0f543597cf81dfdd30eca03843666"}, + {file = "rapidfuzz-2.15.2-cp310-cp310-win_arm64.whl", hash = "sha256:49972e202251ba60de41a7add8e86a055478020eabf3339300f46a8fdc35d048"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29352510bcc2b7c3c7f3c1ab6f4c2115dc640cd79a9dc8e01adbae19fb96d359"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae3f741b9b3e95908158e6e56a5f11c1abc51754801dccd495e5cba734c541e"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a716bbded611cc82f7b27dcd7335b7bae49706c97a8738283464ff1536e7407"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ff36fb50f02259402d7cbdc96f75671b2cb14550db5ad6534a09a7f4940d796"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d60a2368e2564155d7209143a6b1dafa1eb457f31cf44698f917cba608d2341f"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c02fd6d75de19633f622daf6584cb6ed3148eac3a2b6b08fd3539c166de2921f"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5c875da0e0c9709dbdc6e33a7f061192e98943817e6d0e1f5d1d8b07050e349"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb74dcfadf0c5f520074455fe51fa0f62876e5473f5f60521d153afef888ef70"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b31f65137e8e45c4fb2dda394bb31598cff8290fb0ce5e66c8cf47d1bc554cb"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:689008633f88cf8802dbd281ac745775aeeee67525d532fcbabda0c8bc5b2e32"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:02fd52352346c965fdc9de9d26f55d61941cc27c876a589eeb3f4efdb7dffdb1"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:454ab8b5c8fc526243133dab013f0a3355efcc1200829cfba7ef56280c7763fc"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fd40f263d1ad1cdd4b657e867654674315eea9abf3fce64269610b7bc81265ee"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-win32.whl", hash = "sha256:66db4817c54a6ca91234959c4f6d0cb1fd943ddfb379ee7f9e6dce99b522554e"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f8eaf74105ffea1d15198b109ff0ca7b6dccafc61e05fa5f98a53d925707c57"}, + {file = "rapidfuzz-2.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed0ec102b5e405d7562e4df05729a89467ae5c8a364c52fcf8c129398e82e6c5"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c0c8475f029a50bf65571b59d332fccd3eb33c5e49283868490a973e9ca7c33c"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ee9ee24eb431d5f73d0b255dc8e66272967a58cd6670cca984a81bbfc7dde904"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1ecd818c108cefea2c02a9a716e223f811e612a050c8625555336b65d1cabef"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3eda119ebcf501dc35054abd9a187b5249b3d93b3965485371efb48e735b72c"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7ba83d0846991f67c2ec12ff8530b5e0f929e32a57352080b5f95aade0a62e"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c279864902a9538b17547e0d9399f05f36ebb9f3356bc5bc4cec2ba137fa5a17"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c94e247011fa7eea14d210123ebda2ecdf98ccc114254353edb4501ee8a19d7"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675c9052b3a04a4b33c92f0b8952ef2439163853422cc583286351ee82fc4d26"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d64820ae7a795082208a2d762c6a291aca116b86e35c2831e468ae3d4bb5cd"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c0f12cc4a8216edfaa0511aae34d8b2f824a05cfe5a26a08de9cf180ae584e88"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e27da009ef39dc64297bcdf09c8d4c79ac90d0015fcf0a01af2a802cd7e1803"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ea541d56fbb7de717a013790c2bce655252da220f23db0c6ce24f628cbe228e6"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f52338e4e69aff4260c84275c7a704d198315b9b84303e67e584971409347d0"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-win32.whl", hash = "sha256:d5550e0078b2618c4ea7ea761053337eb7c5f5cc515f4941d8108ce9b0c7ee8c"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:19f72cfe2553c83c5e383851aba2891dafbb6446b6ae1ec0637333558ddd564e"}, + {file = "rapidfuzz-2.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:423ef2ca785da77cd081d5bbc57035dc9b91500008a1b8e8e811a0ba3871a5ee"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0a02f1b08879a74aa7b4e562823f67a2e913fe3bd18c5346d9270d16fc588500"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a100ca26804b9ac2b2c0f70c632102bc0005d2cafe6d748f5d01dbe569c378bf"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e9fb88659cff92eba1b441efe426a4c349372137ee713b3a3933cc6ead73234"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58073d3ebed8c0f51e163654dcb5e34f1e8b67f7b23361441861c6021243184b"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f55ad06ff79c2ffa3d1f5b38ce8f3082fa4db57c04be7de85243bd0625ca4ef"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceecb57ec9e5c0d5bd9bd2881731c59cdc9a2c51711fd0b29b5bf14bdcab465f"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6c32c855e16ef3890037569f6f1299857172c674cd8946244e5fb7d5cacb771a"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e46f82fda6f969da8be5a8f33a057b2a9c6e7b80ab8679344a72e6fb708a48fc"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6edc9b138797c60c1276171d8c97f53b17e304ade37c022ff97b1e995f79ba79"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b32e4fd756a32f92b6f8b707a682ab4054b90c835021c01d81baba22f6277172"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5fb89d3a8d389eca258aba913adc81a8b8231b48896abbcb2f05768455584c4e"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-win32.whl", hash = "sha256:03ceea6cc9e4442379aa8581fbe61bad6e12d7938b16fbdc8442c8d915ad1154"}, + {file = "rapidfuzz-2.15.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cb9f24fafb5ed77fc2ce23b1d8351efcfdb4c05b5f3b96bf004e89344a3d30ed"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aab133bea22acbd3fa3740989a2f21d0e275efede2bf406a25a84392086c32f9"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e110224e0de4fe4876224104a79550d18df15459fe94adf24b4b644e31d69cc"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:780b006bd007e4a071a9c022733f56b0df1f8c269bb7e9dbe079a79e8d9d3b8d"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:898bee3fd785ee695d4cb0d3c689407809cafca472851904aa78143ca6634903"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34623f51ed5dcbb2ddb97b2fefda34e7b53a047c71aac5ec6b72e42d5263f8b2"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02b3612c9318006290e6e6d82f1f98b83aa4cf062075c5ea03fac71ba4d31499"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dd0aab9ffab0010ae28b60f64c98c09c93086b3dc0cb3da863e53a3ca14a2bd"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e772677a84a166531f975301cb91db234a56eb5b6785e79ff5cb335251580efc"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7a670aed23d9a8d27a0031fa059e8f50f3f7287bd5a075a448251029794de9"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:830f799e5ec534633dee3b26c6d5398461dd3ced22118ab590f7fd0f91263058"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e427a9c9c1a8adac7b0293ddfe8f5885edf4f425cfd8a3b7ceae20434ec0663c"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3a3df80a264a999a120e637f98a1460d4f2c815323dd605e2022eef97db55448"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1496540d2ce8b1b9f340e652b9306674fa657d8d3a0b9629421cf31ace219092"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-win32.whl", hash = "sha256:aabd9da406fec009c08d2cd1bfa444ee568edf8e7c9a9d5e609885fc81c243a3"}, + {file = "rapidfuzz-2.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:d21c66b15fbe253d48399a9d9db361ab2b3462a59b78c9279d9d7d347f5ded91"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ef4dea11b87234e8b08ee47df9d869ae071bdacb5e55df82673ab9fa622f1e0"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ee3d9bc953f232bffcbd973137505f6cf5be5ed9c2cdc5e4a5db4be33bf5a734"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb94f6adbbbdacac9f687eb151ae9220ee9f141bb259fe07e82a2087114c17e"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9c3e07d13661871aebc325b9b3acbd42355a1df1e21ad0435fc81980fd20607"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01bae563a010900abba857e485c3747a78d61c88431cc3d9bea894c7c3e521f"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09187df670e344468597b2c6f5ddc7651be75c4b594baa62c9261a144e5c058"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcbfe5497c93a1b8717ea38b41b47f7e9d155fbc36a6bbfa84b8c901875465af"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f997a93b85c5798fe139a46c68c85de06ff75b4fd52d52463e46573bff39774"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199676b8a19746017a0fbad0eb11380cbda4f635b6d2ee477544743b7f99d947"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:499a170088049258d5118bff8cf88f88ef6054544edbea0f2920eba8669e5eb9"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a69ebe7b493557c425ca1d64bf0b5599f0405772b5179070adc2f62f7867836f"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00bd97cd31aad049400b70e0872b54457c4769b296176d5b064f6a5d6391909f"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cadabe1287314bc5053f57c6043df04e33cf5fba33514ca0f4c7b0b8476063a0"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-win32.whl", hash = "sha256:301709491a7960473c34501602cd85a7653df7e0d4189c0ded1e0fd86a83b6ca"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c968a2330b6f2de93e6d54ef7ebd5e5724ee730cd6f225e977cebc7af1df366"}, + {file = "rapidfuzz-2.15.2-cp39-cp39-win_arm64.whl", hash = "sha256:c6776c27385f3fe5810f3c389f01957d5fa6c3c7f7a76fd9815f2933674f787f"}, + {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0b4c632b684478fd8780970685a0c575a5bee65692727ff9898acf75d61cb3ff"}, + {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b1cfca399461e1f534fbeb3c87f39f2c37ed71f8d1dfb02b78a5b3f81bf0ef"}, + {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba35ec7256a86270a5e2d193ff0089cf84787a1aa94a48f5f6105f86feb8ca38"}, + {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdfc137bbe2e942321f725004395444d2594077932ad55f927d6b6e884c09142"}, + {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:153366a00ea22e79f051298fb9606bf9472bca5ce1b82319070fcbea2f7b97d7"}, + {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6bf1c60432755ed8ab5870a932b7c9382435a240d727d3b5e68f9ff9f83a3556"}, + {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a358eb275eadad0ac44f0fdb2255d6b373908c742f94e06b2190dbfaaaaa49b8"}, + {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34136ab5bbd1b9643f9072102a88471995100b5d734cfaa946d3b63e332e653"}, + {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:796e53c5f78c159aff8e5003bca41bfe007c6a63ee7e7a289765a7db30429197"}, + {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2ce4a91be05c28b57d5019b09cf0970305760623e34da95f2cddd9067e7fe91d"}, + {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:237d5b4cbfacdef0a84f2ead0b4819c586bb74d05f4a380bd2f8489464b7b7fa"}, + {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773dff970af0474d7d551a953a0075840ced30315d4885e038a289857ed33365"}, + {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c536fbbebb496a76cac3a45f139bf023807b1fb6e2262e77f875fc9b6802ec4e"}, + {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e85579a698c9436c2dac1583d4b07cca635faeb9a7adeab03d42938ec0fe9f58"}, + {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:77c540546c0ea7cb229cd9823f9cd174c93988657727880bfdd6db7f353f93d6"}, + {file = "rapidfuzz-2.15.2.tar.gz", hash = "sha256:bfc1d38a7adcbe8912f980a5f46f27a801dd8655582ff0d4a2c0431c02b7ce33"}, ] [package.extras] full = ["numpy"] +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + [[package]] name = "regex" -version = "2023.6.3" +version = "2023.10.3" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"}, - {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"}, - {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"}, - {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"}, - {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"}, - {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"}, - {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"}, - {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"}, - {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"}, - {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"}, - {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"}, - {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"}, - {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"}, - {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"}, - {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"}, - {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"}, - {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"}, - {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"}, - {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"}, - {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"}, - {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"}, - {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"}, - {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"}, - {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"}, - {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"}, - {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"}, - {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"}, - {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"}, - {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"}, - {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"}, - {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"}, - {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, ] [[package]] @@ -3659,6 +3807,114 @@ lint = ["flake8 (==3.4.1)"] rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] +[[package]] +name = "rpds-py" +version = "0.10.6" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, + {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, + {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, + {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, + {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, + {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, + {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, + {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, + {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, + {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, + {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, + {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, + {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, + {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, + {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, + {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, + {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, + {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, + {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, + {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, + {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, +] + [[package]] name = "safe-pysha3" version = "1.0.4" @@ -3701,29 +3957,29 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shellingham" -version = "1.5.0.post1" +version = "1.5.3" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" files = [ - {file = "shellingham-1.5.0.post1-py2.py3-none-any.whl", hash = "sha256:368bf8c00754fd4f55afb7bbb86e272df77e4dc76ac29dbcbb81a59e9fc15744"}, - {file = "shellingham-1.5.0.post1.tar.gz", hash = "sha256:823bc5fb5c34d60f285b624e7264f4dda254bc803a3774a147bf99c0e3004a28"}, + {file = "shellingham-1.5.3-py2.py3-none-any.whl", hash = "sha256:419c6a164770c9c7cfcaeddfacb3d31ac7a8db0b0f3e9c1287679359734107e9"}, + {file = "shellingham-1.5.3.tar.gz", hash = "sha256:cb4a6fec583535bc6da17b647dd2330cf7ef30239e05d547d99ae3705fd0f7f8"}, ] [[package]] @@ -3750,52 +4006,60 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.20" +version = "2.0.22" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"}, - {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"}, - {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"}, - {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"}, - {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"}, - {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"}, - {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"}, - {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, + {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, + {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, + {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, + {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, ] [package.dependencies] @@ -3828,13 +4092,13 @@ sqlcipher = ["sqlcipher3-binary"] [[package]] name = "stack-data" -version = "0.6.2" +version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" files = [ - {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, - {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] @@ -3847,12 +4111,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "titanoboa" -version = "0.1.7" +version = "0.1.8" description = "A Vyper interpreter" optional = false python-versions = "*" -files = [] -develop = false +files = [ + {file = "titanoboa-0.1.8-py3-none-any.whl", hash = "sha256:6551663ba52793d8a2113ab1a169de32ed2c102166a5655e2988af7e9cb8997e"}, + {file = "titanoboa-0.1.8.tar.gz", hash = "sha256:5fc3a6638ade9885d1d271367d4e05d91bdce246e7064882196ccd213ab927cd"}, +] [package.dependencies] eth-abi = "*" @@ -3860,20 +4126,14 @@ eth-account = "*" eth-stdlib = "*" eth-typing = "*" hypothesis = "*" -py-evm = ">=0.7.0a2" +py-evm = ">=0.7.0a4" pytest = "*" requests = "*" rich = "*" -vyper = ">=0.3.8" +vyper = ">=0.3.10" [package.extras] -forking-recommended = ["plyvel", "ujson"] - -[package.source] -type = "git" -url = "https://github.com/vyperlang/titanoboa.git" -reference = "40f5bfcc2afe212bb5a6f5026148f3625596ded7" -resolved_reference = "40f5bfcc2afe212bb5a6f5026148f3625596ded7" +forking-recommended = ["ujson"] [[package]] name = "tomli" @@ -3888,13 +4148,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.8" +version = "0.12.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, - {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] @@ -3930,18 +4190,18 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.9.0" +version = "5.11.2" description = "Traitlets Python configuration system" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, - {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, + {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, + {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "trie" @@ -3969,39 +4229,39 @@ test = ["hypothesis (>=6.56.4,<7)", "pycryptodome", "pytest (>=7.0.0)", "pytest- [[package]] name = "trove-classifiers" -version = "2023.5.24" +version = "2023.9.19" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2023.5.24.tar.gz", hash = "sha256:fd5a1546283be941f47540a135bdeae8fb261380a6a204d9c18012f2a1b0ceae"}, - {file = "trove_classifiers-2023.5.24-py3-none-any.whl", hash = "sha256:d9d7ae14fb90bf3d50bef99c3941b176b5326509e6e9037e622562d6352629d0"}, + {file = "trove-classifiers-2023.9.19.tar.gz", hash = "sha256:3e700af445c802f251ce2b741ee78d2e5dfa5ab8115b933b89ca631b414691c9"}, + {file = "trove_classifiers-2023.9.19-py3-none-any.whl", hash = "sha256:55460364fe248294386d4dfa5d16544ec930493ecc6bd1db07a0d50afb37018e"}, ] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" -version = "1.26.16" +version = "1.26.17" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, - {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, + {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, + {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -4017,23 +4277,23 @@ files = [ [[package]] name = "virtualenv" -version = "20.23.1" +version = "20.24.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, - {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] -distlib = ">=0.3.6,<1" -filelock = ">=3.12,<4" -platformdirs = ">=3.5.1,<4" +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "vyper" @@ -4101,24 +4361,24 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcwidth" -version = "0.2.6" +version = "0.2.8" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, + {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, + {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, ] [[package]] name = "web3" -version = "6.9.0" +version = "6.11.0" description = "web3.py" optional = false python-versions = ">=3.7.2" files = [ - {file = "web3-6.9.0-py3-none-any.whl", hash = "sha256:3bc95043ee9fc6ee0b13a4766d4975b9f7cae069db136430a3799ed18743e608"}, - {file = "web3-6.9.0.tar.gz", hash = "sha256:cb454d0180e63ba1d83143dccf7c623581ba58e222edb006f48252d8a7b948e0"}, + {file = "web3-6.11.0-py3-none-any.whl", hash = "sha256:44e79da6a4765eacf137f2f388e37aa0c1e24a93bdfb462cffe9441d1be3d509"}, + {file = "web3-6.11.0.tar.gz", hash = "sha256:050dea52ae73d787272e7ecba7249f096595938c90cce1a384c20375c6b0f720"}, ] [package.dependencies] @@ -4141,10 +4401,10 @@ typing-extensions = ">=4.0.1" websockets = ">=10.0.0" [package.extras] -dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] +dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (==1.4.1)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] ipfs = ["ipfshttpclient (==0.8.0a2)"] -linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (>=1.0.0)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] +linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==1.4.1)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] [[package]] @@ -4239,28 +4499,35 @@ files = [ [[package]] name = "wheel" -version = "0.40.0" +version = "0.41.2" description = "A built-package format for Python" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.40.0-py3-none-any.whl", hash = "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"}, - {file = "wheel-0.40.0.tar.gz", hash = "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873"}, + {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, + {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, ] [package.extras] -test = ["pytest (>=6.0.0)"] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "wmctrl" -version = "0.4" +version = "0.5" description = "A tool to programmatically control windows inside X" optional = false -python-versions = "*" +python-versions = ">=2.7" files = [ - {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, + {file = "wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7"}, + {file = "wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962"}, ] +[package.dependencies] +attrs = "*" + +[package.extras] +test = ["pytest"] + [[package]] name = "wrapt" version = "1.15.0" @@ -4518,20 +4785,20 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.15.0" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8fe9e234f49d624b6796c4e8f6eb71b98972ecf8f4155cc5a2ce231e2b825a90" +content-hash = "cc845fc44050fac852d04626b39b9dc33546a6d4b173f2e607dff2608105c4a0" diff --git a/pyproject.toml b/pyproject.toml index c8eaf46b..e0a22a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "40f5bfcc2afe212bb5a6f5026148f3625596ded7"} +titanoboa = "0.1.8" vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py new file mode 100644 index 00000000..85e6b8e8 --- /dev/null +++ b/scripts/deploy_infra.py @@ -0,0 +1,89 @@ +import os +import sys + +import boa +import deployment_utils as deploy_utils +from boa.network import NetworkEnv +from eth_account import Account +from rich.console import Console as RichConsole + +logger = RichConsole(file=sys.stdout) + + +def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: + + with open(contract_file, "r") as f: + source = f.read() + + if "ethereum" in network and "# pragma evm-version paris" in source: + source.replace("# pragma evm-version paris", "# pragma evm-version shanghai") + elif "ethereum" not in network and "# pragma evm-version shanghai" in source: + source.replace("# pragma evm-version shanghai", "# pragma evm-version paris") + + return boa.loads_partial(source_code=source) + + +def deploy_infra(network, url, account, fork=False): + + if fork: + boa.env.fork(url) + else: + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + for _network, data in deploy_utils.curve_dao_network_settings.items(): + + if _network in network: + + owner = data.dao_ownership_contract + fee_receiver = data.fee_receiver_address + + assert owner, f"Curve's DAO contracts may not be on {network}." + assert fee_receiver, f"Curve's DAO contracts may not be on {network}." + + # --------------------- Deploy math, views, blueprints --------------------- + + logger.log("Setting EVM versions ...") + + # get source and set evm_version + math_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGMath.vy", network) + views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) + plain_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNG.vy", network) + meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) + gauge_contract_obj = set_evm_version("./contracts/main/LiquidityGauge.vy", network) + + # deploy non-blueprint contracts: + logger.log("Deploying non-blueprint AMM components ...") + math_contract = math_contract_obj.deploy() + views_contract = views_contract_obj.deploy() + + # deploy blueprints: + logger.log("Deploying blueprints ...") + plain_blueprint = plain_contract_obj.deploy_as_blueprint() + meta_blueprint = meta_contract_obj.deploy_as_blueprint() + gauge_blueprint = gauge_contract_obj.deploy_as_blueprint() + + # Factory: + factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) + logger.log("Deploying factory ...") + factory = factory_contract_obj.deploy(fee_receiver, deploy_utils.FIDDYDEPLOYER) + + # Set it all up: + logger.log("Integrating AMM components into factory ...") + factory.set_gauge_implementation(gauge_blueprint.address) + factory.set_views_implementation(views_contract.address) + factory.set_math_implementation(math_contract.address) + factory.set_pool_implementations(0, plain_blueprint.address) + factory.set_metapool_implementations(0, meta_blueprint.address) + + +def main(): + deploy_infra( + "ethereum:sepolia", + "https://eth-sepolia.g.alchemy.com/v2/{alchemy_key}", + "FIDDYDEPLOYER", + False, # forkmode + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 066891e6..120f5c2f 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -51,6 +51,7 @@ def deploy_blueprint(contract, account): GAUGE_CONTROLLER = "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB" ADDRESS_PROVIDER = "0x0000000022d53366457f9d5e68ec105046fc4383" FIDDYRESEARCH = "0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17" +FIDDYDEPLOYER = "0x2d12D0907A388811e3AA855A550F959501d303EE" # -------------- CURVE DATA -------------- From c3bd2c6832b8b80682df7e8b39acffa6a13cf7bc Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:02:25 +0200 Subject: [PATCH 207/337] add gnosis deployment --- README.MD | 8 ++++ poetry.lock | 16 ++++--- pyproject.toml | 2 +- scripts/deploy_infra.py | 103 ++++++++++++++++++++++++++++++++-------- scripts/deploy_pool.py | 34 +++++++++++++ 5 files changed, 137 insertions(+), 26 deletions(-) create mode 100644 scripts/deploy_pool.py diff --git a/README.MD b/README.MD index 86a1edce..8f10ba8a 100644 --- a/README.MD +++ b/README.MD @@ -15,6 +15,14 @@ For integrators: check exchange_received. That should improve your pathing signi 5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) 6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x8a00365ae28d75b92ec695d5a041b744f140438d](https://sepolia.etherscan.io/address/0x8a00365ae28d75b92ec695d5a041b744f140438d#code) +## Gnosis + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://gnosisscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://gnosisscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://gnosisscan.io/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4](https://gnosisscan.io/address/0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b](https://gnosisscan.io/address/0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b#code) + ## Overview The metapool factory has several core components: diff --git a/poetry.lock b/poetry.lock index 4d684944..297ebbb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4115,15 +4115,13 @@ version = "0.1.8" description = "A Vyper interpreter" optional = false python-versions = "*" -files = [ - {file = "titanoboa-0.1.8-py3-none-any.whl", hash = "sha256:6551663ba52793d8a2113ab1a169de32ed2c102166a5655e2988af7e9cb8997e"}, - {file = "titanoboa-0.1.8.tar.gz", hash = "sha256:5fc3a6638ade9885d1d271367d4e05d91bdce246e7064882196ccd213ab927cd"}, -] +files = [] +develop = false [package.dependencies] eth-abi = "*" eth-account = "*" -eth-stdlib = "*" +eth-stdlib = ">=0.2.7,<0.3.0" eth-typing = "*" hypothesis = "*" py-evm = ">=0.7.0a4" @@ -4135,6 +4133,12 @@ vyper = ">=0.3.10" [package.extras] forking-recommended = ["ujson"] +[package.source] +type = "git" +url = "https://github.com/vyperlang/titanoboa.git" +reference = "3201bf318d10c238071365be833ef8e3c7eaaef2" +resolved_reference = "3201bf318d10c238071365be833ef8e3c7eaaef2" + [[package]] name = "tomli" version = "2.0.1" @@ -4801,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "cc845fc44050fac852d04626b39b9dc33546a6d4b173f2e607dff2608105c4a0" +content-hash = "4bd0c70b9ebc5d3a0d92e626ea31898516bb129fd5f2ca0abaee77e4fd77565f" diff --git a/pyproject.toml b/pyproject.toml index e0a22a08..0898b544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = "0.1.8" +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "3201bf318d10c238071365be833ef8e3c7eaaef2"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 85e6b8e8..75dbbc2c 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -4,25 +4,66 @@ import boa import deployment_utils as deploy_utils from boa.network import NetworkEnv +from eth_abi import encode from eth_account import Account from rich.console import Console as RichConsole logger = RichConsole(file=sys.stdout) +deployments = { + "ethereum:sepolia": { + "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", + "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", + "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", + "meta_amm": "0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e", + "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", + "factory": "0x8a00365ae28d75b92ec695d5a041b744f140438d", + }, + "gnosis:mainnet": { + "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, +} + def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: with open(contract_file, "r") as f: source = f.read() - if "ethereum" in network and "# pragma evm-version paris" in source: - source.replace("# pragma evm-version paris", "# pragma evm-version shanghai") - elif "ethereum" not in network and "# pragma evm-version shanghai" in source: - source.replace("# pragma evm-version shanghai", "# pragma evm-version paris") + is_shanghai_chain = any([x in network for x in ["ethereum", "gnosis"]]) + + if is_shanghai_chain and "# pragma evm-version paris" in source: + logger.log("Replacing EVM version to Shanghai ...") + source = source.replace("# pragma evm-version paris\n", "# pragma evm-version shanghai\n") + elif not is_shanghai_chain and "# pragma evm-version shanghai" in source: + logger.log("Replacing EVM version to Paris ...") + source = source.replace("# pragma evm-version shanghai\n", "# pragma evm-version paris\n") return boa.loads_partial(source_code=source) +def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): + + deployed_contract = deployments[network][contract_designation] + + if not deployed_contract: + logger.log(f"Deploying {contract_designation} contract ...") + if not blueprint: + contract = contract_obj.deploy(*args) + else: + contract = contract_obj.deploy_as_blueprint() + logger.log(f"Deployed! At: {contract.address}.") + else: + logger.log(f"Deployed {contract_designation} contract exists. Using {deployed_contract} ...") + contract = contract_obj.at(deployed_contract) + + return contract + + def deploy_infra(network, url, account, fork=False): if fork: @@ -49,37 +90,61 @@ def deploy_infra(network, url, account, fork=False): views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) plain_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNG.vy", network) meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) - gauge_contract_obj = set_evm_version("./contracts/main/LiquidityGauge.vy", network) # deploy non-blueprint contracts: logger.log("Deploying non-blueprint AMM components ...") - math_contract = math_contract_obj.deploy() - views_contract = views_contract_obj.deploy() + math_contract = check_and_deploy(math_contract_obj, "math", network) + views_contract = check_and_deploy(views_contract_obj, "views", network) # deploy blueprints: logger.log("Deploying blueprints ...") - plain_blueprint = plain_contract_obj.deploy_as_blueprint() - meta_blueprint = meta_contract_obj.deploy_as_blueprint() - gauge_blueprint = gauge_contract_obj.deploy_as_blueprint() + plain_blueprint = check_and_deploy(plain_contract_obj, "plain_amm", network, blueprint=True) + meta_blueprint = check_and_deploy(meta_contract_obj, "meta_amm", network, blueprint=True) # Factory: factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) logger.log("Deploying factory ...") - factory = factory_contract_obj.deploy(fee_receiver, deploy_utils.FIDDYDEPLOYER) + args = [fee_receiver, deploy_utils.FIDDYDEPLOYER] + factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) - # Set it all up: + constructor_args = encode(["address", "address"], args) + logger.log(f"Constructor arguments for factory: {constructor_args.hex()}") + + # Set up AMM implementations: logger.log("Integrating AMM components into factory ...") - factory.set_gauge_implementation(gauge_blueprint.address) - factory.set_views_implementation(views_contract.address) - factory.set_math_implementation(math_contract.address) - factory.set_pool_implementations(0, plain_blueprint.address) - factory.set_metapool_implementations(0, meta_blueprint.address) + + if not factory.views_implementation() == views_contract.address: + factory.set_views_implementation(views_contract.address) + logger.log(f"Set views implementation to: {views_contract.address}") + + if not factory.math_implementation() == math_contract.address: + factory.set_math_implementation(math_contract.address) + logger.log(f"Set math implementation to: {math_contract.address}") + + if not factory.pool_implementations(0) == plain_blueprint.address: + factory.set_pool_implementations(0, plain_blueprint.address) + logger.log(f"Set plain amm implementation to: {plain_blueprint.address}") + + if not factory.metapool_implementations(0) == meta_blueprint.address: + factory.set_metapool_implementations(0, meta_blueprint.address) + logger.log(f"Set meta amm implementation to: {meta_blueprint.address}") + + if "ethereum" in network: # Gauge contract only for Ethereum. + logger.log("Deploying and setting up Gauge contracts ...") + gauge_contract_obj = set_evm_version("./contracts/main/LiquidityGauge.vy", network) + gauge_blueprint = check_and_deploy(gauge_contract_obj, "gauge", network, blueprint=True) + + if not factory.gauge_implementation() == gauge_blueprint.address: + factory.set_gauge_implementation(gauge_blueprint.address) + logger.log(f"Set liquidity gauge implementation to: {gauge_blueprint.address}") + + logger.log("Infra deployed!") def main(): deploy_infra( - "ethereum:sepolia", - "https://eth-sepolia.g.alchemy.com/v2/{alchemy_key}", + "gnosis:mainnet", + "https://gnosis.api.onfinality.io/public", "FIDDYDEPLOYER", False, # forkmode ) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py new file mode 100644 index 00000000..a60d290b --- /dev/null +++ b/scripts/deploy_pool.py @@ -0,0 +1,34 @@ +import os +import sys + +import boa +from boa.network import NetworkEnv +from eth_account import Account +from rich.console import Console as RichConsole + +logger = RichConsole(file=sys.stdout) + + +def deploy_plain_pool(network, url, account, factory, fork=False): + + if fork: + boa.env.fork(url) + else: + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy").at(factory) + + +def main(): + deploy_plain_pool( + "ethereum:sepolia", + "https://eth-sepolia.g.alchemy.com/v2/{alchemy_key}", + "FIDDYDEPLOYER", + "0x8a00365ae28d75b92ec695d5a041b744f140438d", + False, # forkmode + ) + + +if __name__ == "__main__": + main() From af7685d3f1389ab6a244f9c35c91603b10dcfa48 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:48:38 +0200 Subject: [PATCH 208/337] fix: doubly adding asset types in add_base_pool --- README.MD | 4 +- contracts/main/CurveStableSwapFactoryNG.vy | 1 - scripts/deploy_infra.py | 81 ++++++++++++++++++++-- scripts/deploy_pool.py | 8 ++- scripts/deployment_utils.py | 14 +++- scripts/set_up_base_pools.py | 53 ++++++++++++++ 6 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 scripts/set_up_base_pools.py diff --git a/README.MD b/README.MD index 8f10ba8a..4fdd4e5e 100644 --- a/README.MD +++ b/README.MD @@ -13,7 +13,7 @@ For integrators: check exchange_received. That should improve your pathing signi 3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd](https://sepolia.etherscan.io/address/0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd#code) 4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e](https://sepolia.etherscan.io/address/0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e#code) 5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) -6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x8a00365ae28d75b92ec695d5a041b744f140438d](https://sepolia.etherscan.io/address/0x8a00365ae28d75b92ec695d5a041b744f140438d#code) +6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xfb37b8D939FFa77114005e61CFc2e543d6F49A81](https://sepolia.etherscan.io/address/0xfb37b8D939FFa77114005e61CFc2e543d6F49A81#code) ## Gnosis @@ -21,7 +21,7 @@ For integrators: check exchange_received. That should improve your pathing signi 2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://gnosisscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) 3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://gnosisscan.io/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) 4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4](https://gnosisscan.io/address/0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b](https://gnosisscan.io/address/0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8](https://gnosisscan.io/address/0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8#code) ## Overview diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index f12221c2..23c55dd5 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -750,7 +750,6 @@ def add_base_pool( coin = CurvePool(_base_pool).coins(i) assert coin != 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE # dev: native token is not supported self.base_pool_data[_base_pool].coins.append(coin) - self.base_pool_data[_base_pool].asset_types.append(_asset_types[i]) self.base_pool_assets[coin] = True decimals += (ERC20(coin).decimals() << i*8) self.base_pool_data[_base_pool].decimals = decimals diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 75dbbc2c..589f556d 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -17,14 +17,84 @@ "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", "meta_amm": "0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e", "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", - "factory": "0x8a00365ae28d75b92ec695d5a041b744f140438d", + "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", }, "gnosis:mainnet": { "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", - "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + }, + "polygon:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "arbitrum:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "optimism:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "base:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "avax:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "aurora:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "celo:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "fantom:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "kava:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "moonbeam:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", }, } @@ -103,7 +173,6 @@ def deploy_infra(network, url, account, fork=False): # Factory: factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) - logger.log("Deploying factory ...") args = [fee_receiver, deploy_utils.FIDDYDEPLOYER] factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) @@ -143,10 +212,10 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - "gnosis:mainnet", - "https://gnosis.api.onfinality.io/public", + ":mainnet", + "", "FIDDYDEPLOYER", - False, # forkmode + fork=False, ) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index a60d290b..24d966f1 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -6,6 +6,8 @@ from eth_account import Account from rich.console import Console as RichConsole +from scripts.deploy_infra import deployments + logger = RichConsole(file=sys.stdout) @@ -22,10 +24,10 @@ def deploy_plain_pool(network, url, account, factory, fork=False): def main(): deploy_plain_pool( - "ethereum:sepolia", - "https://eth-sepolia.g.alchemy.com/v2/{alchemy_key}", + "gnosis:mainnet", + "https://gnosis.drpc.org", "FIDDYDEPLOYER", - "0x8a00365ae28d75b92ec695d5a041b744f140438d", + deployments["gnosis:mainnet"]["factory"], False, # forkmode ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 120f5c2f..810f396f 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -191,7 +191,19 @@ class BasePoolSettings: ], "arbitrum:mainnet": [], "optimism:mainnet": [], - "gnosis:mainnet": [], + "gnosis:mainnet": [ + BasePoolSettings( + pool="0x7f90122bf0700f9e7e1f688fe926940e8839f353", + lp_token="0x1337BedC9D22ecbe766dF105c9623922A27963EC", + coins=[ + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", + "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", + ], + asset_types=[0, 0, 0], + n_coins=3, + ), + ], "polygon:mainnet": [], "base:mainnet": [], } diff --git a/scripts/set_up_base_pools.py b/scripts/set_up_base_pools.py new file mode 100644 index 00000000..3f477e34 --- /dev/null +++ b/scripts/set_up_base_pools.py @@ -0,0 +1,53 @@ +import os +import sys + +import boa +from boa.network import NetworkEnv +from deploy_infra import deployments +from deployment_utils import base_pool_list +from eth_account import Account +from rich.console import Console as RichConsole + +logger = RichConsole(file=sys.stdout) + + +def set_up_base_pools(network, url, account, factory, fork: bool = False): + + if fork: + boa.env.fork(url) + else: + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy").at(factory) + + logger.log("Setting up base pools ...") + base_pool_data = base_pool_list[network] + onboarded_base_pools = [factory.base_pool_list(i) for i in range(factory.base_pool_count())] + + if base_pool_data: # check if network has base pools: + for data in base_pool_data: + if data.pool not in onboarded_base_pools: + factory.add_base_pool( + data.pool, + data.lp_token, + data.asset_types, + data.n_coins, + ) + logger.log(f"Added {data.pool} to factory {factory.address} on {network}.") + + assert factory.base_pool_data(data.pool)[0] == data.pool + + +def main(): + set_up_base_pools( + "gnosis:mainnet", + "https://gnosis.drpc.org", + "FIDDYDEPLOYER", + deployments["gnosis:mainnet"]["factory"], + False, # forkmode + ) + + +if __name__ == "__main__": + main() From c3a25b1f7a975939cd04a69c14df23f579eca59a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:21:02 +0200 Subject: [PATCH 209/337] attempt contract verification --- scripts/deploy_pool.py | 32 +++++++++++++++++++++++++++----- scripts/deployment_utils.py | 6 +++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 24d966f1..7fa72b6d 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -3,15 +3,16 @@ import boa from boa.network import NetworkEnv +from deploy_infra import deployments +from deployment_utils import pool_settings +from eth_abi import encode from eth_account import Account from rich.console import Console as RichConsole -from scripts.deploy_infra import deployments - logger = RichConsole(file=sys.stdout) -def deploy_plain_pool(network, url, account, factory, fork=False): +def deploy_plain_pool(network, url, account, fork=False): if fork: boa.env.fork(url) @@ -19,7 +20,29 @@ def deploy_plain_pool(network, url, account, factory, fork=False): boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) - factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy").at(factory) + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy") + factory = factory.at(deployments["gnosis:mainnet"]["factory"]) + + args = pool_settings[network]["plain"] + call_sig = [ + "string", + "string", + "address[]", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint8[]", + "bytes4[]", + "address[]", + ] + encoded_args = encode(call_sig, args) + print(f"Encoded args: {encoded_args.hex()}") + breakpoint() + logger.log("Deploying pool ...") + amm_address = factory.deploy_plain_pool(*args) + logger.log(f"Deployed Plain pool {amm_address}.") def main(): @@ -27,7 +50,6 @@ def main(): "gnosis:mainnet", "https://gnosis.drpc.org", "FIDDYDEPLOYER", - deployments["gnosis:mainnet"]["factory"], False, # forkmode ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 810f396f..a86c2f58 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -231,7 +231,7 @@ class PoolSettings: pool_settings = { "gnosis:mainnet": { "plain": [ - "WXDAI<>USDC<>USDT", # name + "WXDAI/USDC/USDT", # name "3pool-ng", # symbol [ "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", # wxdai @@ -244,7 +244,7 @@ class PoolSettings: 865, # ma_exp_time 0, # implementation index [0, 0, 0], # asset_types - [0, 0, 0], # method_ids + [b"", b"", b""], # method_ids [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles ], "oracles": [ @@ -275,7 +275,7 @@ class PoolSettings: 865, # ma_exp_time 0, # implementation index [2, 0], # asset_types - [0, 0], # method_ids + [b"", b""], # method_ids [ZERO_ADDRESS, ZERO_ADDRESS], # oracles ], "meta-plain": [], From c86f511df647cd490402849755f22734509a5fb9 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:21:20 +0200 Subject: [PATCH 210/337] remove breakpoint() --- scripts/deploy_pool.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 7fa72b6d..283fd4b0 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -39,7 +39,6 @@ def deploy_plain_pool(network, url, account, fork=False): ] encoded_args = encode(call_sig, args) print(f"Encoded args: {encoded_args.hex()}") - breakpoint() logger.log("Deploying pool ...") amm_address = factory.deploy_plain_pool(*args) logger.log(f"Deployed Plain pool {amm_address}.") From fb09ee369bea9aaab3d6bc592169cd7122a65bd8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:52:52 +0200 Subject: [PATCH 211/337] finish gnosis deployment testing --- scripts/deploy_pool.py | 33 ++++++++--------------------- scripts/deployment_utils.py | 41 ++++++++++--------------------------- 2 files changed, 19 insertions(+), 55 deletions(-) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 283fd4b0..93355b6a 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -5,14 +5,13 @@ from boa.network import NetworkEnv from deploy_infra import deployments from deployment_utils import pool_settings -from eth_abi import encode from eth_account import Account from rich.console import Console as RichConsole logger = RichConsole(file=sys.stdout) -def deploy_plain_pool(network, url, account, fork=False): +def deploy_pool(network, url, account, fork, pool_type): if fork: boa.env.fork(url) @@ -23,34 +22,18 @@ def deploy_plain_pool(network, url, account, fork=False): factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy") factory = factory.at(deployments["gnosis:mainnet"]["factory"]) - args = pool_settings[network]["plain"] - call_sig = [ - "string", - "string", - "address[]", - "uint256", - "uint256", - "uint256", - "uint256", - "uint256", - "uint8[]", - "bytes4[]", - "address[]", - ] - encoded_args = encode(call_sig, args) - print(f"Encoded args: {encoded_args.hex()}") logger.log("Deploying pool ...") - amm_address = factory.deploy_plain_pool(*args) + args = pool_settings[network][pool_type] + if pool_type == "plain": + amm_address = factory.deploy_plain_pool(*args) + elif pool_type == "meta": + amm_address = factory.deploy_metapool(*args) + logger.log(f"Deployed Plain pool {amm_address}.") def main(): - deploy_plain_pool( - "gnosis:mainnet", - "https://gnosis.drpc.org", - "FIDDYDEPLOYER", - False, # forkmode - ) + deploy_pool("gnosis:mainnet", "https://gnosis.drpc.org", "FIDDYDEPLOYER", False, "meta") # forkmode if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index a86c2f58..4bc9e357 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -4,7 +4,8 @@ import click from ape import networks, project from ape.api.address import Address -from eth_utils import function_signature_to_4byte_selector + +# from eth_utils import function_signature_to_4byte_selector DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -247,39 +248,19 @@ class PoolSettings: [b"", b"", b""], # method_ids [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles ], - "oracles": [ - "WETH<>wstETH", # name - "wstETH-ng", # symbol - [ - "0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6", # wsteth - "0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", # weth - ], - 500, - 1000000, # fee - 20000000000, # offpeg_fee_multiplier - 865, # ma_exp_time - 0, # implementation index - [1, 0], # asset_types - [function_signature_to_4byte_selector("exchangeRate()"), 0], # method_ids - ["0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6", ZERO_ADDRESS], # oracles - ], - "rebasing": [ - "sGNO<>GNO" "sGNO-ng", # name # symbol - [ - "0xA4eF9Da5BA71Cc0D2e5E877a910A37eC43420445", # sGNO - "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb", # GNO - ], - 50, # A, + "meta": [ + "0x7f90122bf0700f9e7e1f688fe926940e8839f353", # base_pool + "EURE/3CRV", # name + "eure3crvng", # symbol + "0xcb444e90d8198415266c6a2724b7900fb12fc56e", # eure + 500, # A 1000000, # fee 20000000000, # offpeg_fee_multiplier 865, # ma_exp_time 0, # implementation index - [2, 0], # asset_types - [b"", b""], # method_ids - [ZERO_ADDRESS, ZERO_ADDRESS], # oracles + 0, # asset_types + b"", # method_ids + ZERO_ADDRESS, # oracles ], - "meta-plain": [], - "meta-oracle": [], - "meta-rebasing": [], } } From cda42d8ddb6d6defb9c3a02f6523569480a42615 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:24:20 +0200 Subject: [PATCH 212/337] remove exchange_underlying_received since bytecode length cannot fit in paris evm-version --- README.MD | 4 +- contracts/main/CurveStableSwapMetaNG.vy | 313 +++++++++++------------- poetry.lock | 6 +- pyproject.toml | 2 +- scripts/deploy_infra.py | 70 ++---- scripts/deployment_utils.py | 36 +-- tests/pools/test_exchange_received.py | 73 ------ 7 files changed, 178 insertions(+), 326 deletions(-) diff --git a/README.MD b/README.MD index 4fdd4e5e..41f07d76 100644 --- a/README.MD +++ b/README.MD @@ -11,7 +11,7 @@ For integrators: check exchange_received. That should improve your pathing signi 1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443](https://sepolia.etherscan.io/address/0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443#code) 2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0](https://sepolia.etherscan.io/address/0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0#code) 3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd](https://sepolia.etherscan.io/address/0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e](https://sepolia.etherscan.io/address/0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://sepolia.etherscan.io/address/#code) 5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) 6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xfb37b8D939FFa77114005e61CFc2e543d6F49A81](https://sepolia.etherscan.io/address/0xfb37b8D939FFa77114005e61CFc2e543d6F49A81#code) @@ -20,7 +20,7 @@ For integrators: check exchange_received. That should improve your pathing signi 1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://gnosisscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) 2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://gnosisscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) 3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://gnosisscan.io/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4](https://gnosisscan.io/address/0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://gnosisscan.io/address/#code) 5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8](https://gnosisscan.io/address/0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8#code) ## Overview diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b2ba6a5e..80ac4b1e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,6 +1,6 @@ # pragma version 0.3.10 # pragma optimize codesize -# pragma evm-version shanghai +# pragma evm-version paris """ @title CurveStableSwapMetaNG @author Curve.Fi @@ -342,8 +342,7 @@ def __init__( # it is by default set to 0; this is fine in the case of these two # immutables, since they are only used if asset_types[0] == 3. call_amount = 10**convert(ERC20Detailed(_coins[0]).decimals(), uint256) - _underlying_asset: address = ERC4626(_coins[0]).asset() - scale_factor = 10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256)) + scale_factor = 10**(18 - convert(ERC20Detailed(ERC4626(_coins[0]).asset()).decimals(), uint256)) # ----------------- Parameters independent of pool type ------------------ @@ -366,8 +365,9 @@ def __init__( for i in range(N_COINS_128): self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - self.admin_balances.append(0) # <--- this initialises storage for admin balances - self.stored_balances.append(0) + + self.admin_balances = [0, 0] + self.stored_balances = [0, 0] # --------------------------- ERC20 stuff ---------------------------- @@ -646,47 +646,93 @@ def exchange_underlying( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - return self._exchange_underlying( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - False - ) + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + dy: uint256 = 0 + base_i: int128 = 0 + base_j: int128 = 0 + meta_i: int128 = 0 + meta_j: int128 = 0 + x: uint256 = 0 + output_coin: address = empty(address) -@external -@nonreentrant('lock') -def exchange_underlying_received( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two underlying coins - @dev This is disabled if pool contains rebasing tokens. - @param i Index value for the underlying coin to send - @param j Index value of the underlying coin to receive - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @param _receiver Address that receives `j` - @return Actual amount of `j` received - """ - assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens - return self._exchange_underlying( - msg.sender, - i, - j, + # ------------------------ Determine coin indices ------------------------ + + # Get input coin indices: + if i > 0: + base_i = i - MAX_METAPOOL_COIN_INDEX + meta_i = 1 + + # Get output coin and indices: + if j == 0: + output_coin = coins[0] + else: + base_j = j - MAX_METAPOOL_COIN_INDEX + meta_j = 1 + output_coin = BASE_COINS[base_j] + + # --------------------------- Do Transfer in ----------------------------- + + # If incoming coin is supposed to go to the base pool, the _transfer_in + # method will add_liquidity in the base pool and return dx_w_fee LP tokens + dx_w_fee: uint256 = self._transfer_in( + meta_i, + base_i, _dx, - _min_dy, - _receiver, - True + msg.sender, + False, + (i > 0 and j > 0), # <--- if True: do not add liquidity to base pool. ) + # ------------------------------- Exchange ------------------------------- + + if i == 0 or j == 0: # meta swap + + if i == 0: + + # xp[i] + dx_w_fee * rates[i] / PRECISION + x = xp[i] + unsafe_div(dx_w_fee * rates[i], PRECISION) + + else: + + # dx_w_fee is the number of base_pool LP tokens minted after + # base_pool.add_liquidity in self._transfer_in: + + # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION + x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) + x += xp[MAX_METAPOOL_COIN_INDEX] + + dy = self.__exchange(x, xp, rates, meta_i, meta_j) + + # Adjust stored balances of meta-level tokens: + self.stored_balances[meta_j] -= dy + + # Withdraw from the base pool if needed + if j > 0: + out_amount: uint256 = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) + dy = ERC20(output_coin).balanceOf(self) - out_amount + + assert dy >= _min_dy + + else: # base pool swap (user should swap at base pool for better gas) + + dy = ERC20(output_coin).balanceOf(self) + StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) + dy = ERC20(output_coin).balanceOf(self) - dy + + # --------------------------- Do Transfer out ---------------------------- + + assert ERC20(output_coin).transfer(_receiver, dy, default_return_value=True) + + # ------------------------------------------------------------------------ + + log TokenExchangeUnderlying(msg.sender, i, _dx, j, dy) + + return dy + @external @nonreentrant('lock') @@ -747,7 +793,7 @@ def add_liquidity( difference: uint256 = 0 new_balance: uint256 = 0 - ys: uint256 = (D0 + D1) / N_COINS + ys: uint256 = unsafe_div(D0 + D1, N_COINS) xs: uint256 = 0 _dynamic_fee_i: uint256 = 0 @@ -783,7 +829,8 @@ def add_liquidity( xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D1 = math.get_D(xp, amp, N_COINS) # <------ Reuse D1 for new D value. - mint_amount = total_supply * (D1 - D0) / D0 + # we do unsafe div here because we already did several safedivs with D0 + mint_amount = unsafe_div(total_supply * (D1 - D0), D0) self.upkeep_oracles(xp, amp, D1) else: @@ -797,7 +844,12 @@ def add_liquidity( # Mint pool tokens total_supply += mint_amount - self.balanceOf[_receiver] += mint_amount + user_lp_token_bal: uint256 = self.balanceOf[_receiver] + + # here we can increase balance using unsafe add because + # user balance will always be <= total_supply. so if total_supply + # safeadd works, this can be safely unsafe: + self.balanceOf[_receiver] = unsafe_add(user_lp_token_bal, mint_amount) self.total_supply = total_supply log Transfer(empty(address), _receiver, mint_amount) @@ -875,8 +927,10 @@ def remove_liquidity_imbalance( self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - ys: uint256 = (D0 + D1) / N_COINS + # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + base_fee: uint256 = unsafe_div(self.fee * N_COINS, 4) + # ys: uint256 = (D0 + D1) / N_COINS + ys: uint256 = unsafe_div(D0 + D1, N_COINS) fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) dynamic_fee: uint256 = 0 @@ -891,9 +945,9 @@ def remove_liquidity_imbalance( new_balance = new_balances[i] if ideal_balance > new_balance: - difference = ideal_balance - new_balance + difference = unsafe_sub(ideal_balance, new_balance) else: - difference = new_balance - ideal_balance + difference = unsafe_sub(new_balance, ideal_balance) # base_fee * difference / FEE_DENOMINATOR xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) @@ -910,7 +964,9 @@ def remove_liquidity_imbalance( self.upkeep_oracles(new_balances, amp, D1) total_supply: uint256 = self.total_supply - burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 + # here we can do unsafe div by D0 because we did several safedivs: + # burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 + burn_amount: uint256 = unsafe_div((D0 - D1) * total_supply, D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -979,7 +1035,7 @@ def remove_liquidity( msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), - total_supply - _burn_amount + unsafe_sub(total_supply, _burn_amount) ) # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- @@ -1006,13 +1062,15 @@ def withdraw_admin_fees(): def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier + + # to remove dynamic fee: just set _offpeg_fee_multiplier less than FEE_DENOMINATOR if _offpeg_fee_multiplier <= FEE_DENOMINATOR: return _fee xps2: uint256 = (xpi + xpj) ** 2 return ( (_offpeg_fee_multiplier * _fee) / - ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + (unsafe_sub(_offpeg_fee_multiplier, FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) ) @@ -1030,14 +1088,21 @@ def __exchange( y: uint256 = math.get_y(i, j, x, _xp, amp, D, N_COINS) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR + dy_fee: uint256 = unsafe_div( + dy * self._dynamic_fee( + unsafe_div(_xp[i] + x, 2), unsafe_div(_xp[j] + y, 2), self.fee + ), + FEE_DENOMINATOR + ) # Convert all to real units dy = (dy - dy_fee) * PRECISION / rates[j] - self.admin_balances[j] += ( - unsafe_div(dy_fee * admin_fee, FEE_DENOMINATOR) # dy_fee * admin_fee / FEE_DENOMINATOR - ) * PRECISION / rates[j] + # admin_fee = dy_fee * admin_fee / FEE_DENOMINATOR + self.admin_balances[j] += unsafe_div( + unsafe_div(dy_fee * admin_fee, FEE_DENOMINATOR) * PRECISION, + rates[j] # we can do unsafediv here because we did safediv before + ) # Calculate and store state prices: xp: DynArray[uint256, MAX_COINS] = _xp @@ -1072,7 +1137,7 @@ def _exchange( # `dx` is whatever the pool received after ERC20 transfer: dx: uint256 = self._transfer_in( i, - -1, # <----- we're not handling underlying coins here. + -1, _dx, sender, expect_optimistic_transfer @@ -1096,105 +1161,6 @@ def _exchange( return dy -@internal -def _exchange_underlying( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - expect_optimistic_transfer: bool = False -) -> uint256: - - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) - - dy: uint256 = 0 - base_i: int128 = 0 - base_j: int128 = 0 - meta_i: int128 = 0 - meta_j: int128 = 0 - x: uint256 = 0 - output_coin: address = empty(address) - - # ------------------------ Determine coin indices ------------------------ - - # Get input coin indices: - if i > 0: - base_i = i - MAX_METAPOOL_COIN_INDEX - meta_i = 1 - - # Get output coin and indices: - if j == 0: - output_coin = coins[0] - else: - base_j = j - MAX_METAPOOL_COIN_INDEX - meta_j = 1 - output_coin = BASE_COINS[base_j] - - # --------------------------- Do Transfer in ----------------------------- - - # If incoming coin is supposed to go to the base pool, the _transfer_in - # method will add_liquidity in the base pool and return dx_w_fee LP tokens - dx_w_fee: uint256 = self._transfer_in( - meta_i, - base_i, - _dx, - sender, - expect_optimistic_transfer, - (i > 0 and j > 0), # <--- if True: do not add liquidity to base pool. - ) - - # ------------------------------- Exchange ------------------------------- - - if i == 0 or j == 0: # meta swap - - if i == 0: - - # xp[i] + dx_w_fee * rates[i] / PRECISION - x = xp[i] + unsafe_div(dx_w_fee * rates[i], PRECISION) - - else: - - # dx_w_fee is the number of base_pool LP tokens minted after - # base_pool.add_liquidity in self._transfer_in - - # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION - x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) - x += xp[MAX_METAPOOL_COIN_INDEX] - - dy = self.__exchange(x, xp, rates, meta_i, meta_j) - - # Adjust stored balances of meta-level tokens: - self.stored_balances[meta_j] -= dy - - # Withdraw from the base pool if needed - if j > 0: - out_amount: uint256 = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).remove_liquidity_one_coin(dy, base_j, 0) - dy = ERC20(output_coin).balanceOf(self) - out_amount - - assert dy >= _min_dy - - else: # base pool swap (user should swap at base pool for better gas) - - dy = ERC20(output_coin).balanceOf(self) - StableSwap(BASE_POOL).exchange(base_i, base_j, dx_w_fee, _min_dy) - dy = ERC20(output_coin).balanceOf(self) - dy - - # --------------------------- Do Transfer out ---------------------------- - - assert ERC20(output_coin).transfer(receiver, dy, default_return_value=True) - - # ------------------------------------------------------------------------ - - log TokenExchangeUnderlying(sender, i, _dx, j, dy) - - return dy - - @internal def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: @@ -1226,11 +1192,9 @@ def _withdraw_admin_fees(): for i in range(N_COINS_128): if admin_balances[i] > 0: - self._transfer_out(i, admin_balances[i], fee_receiver) - admin_balances[i] = 0 - self.admin_balances = admin_balances + self.admin_balances = [0, 0] # --------------------------- AMM Math Functions ----------------------------- @@ -1250,9 +1214,9 @@ def _A() -> uint256: t0: uint256 = self.initial_A_time # Expressions in uint256 cannot have negative numbers, thus "if" if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + return A0 + unsafe_sub(A1, A0) * (block.timestamp - t0) / (t1 - t0) else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + return A0 - unsafe_sub(A0, A1) * (block.timestamp - t0) / (t1 - t0) else: # when t1 == 0 or block.timestamp >= t1 return A1 @@ -1312,8 +1276,10 @@ def _calc_withdraw_one_coin( new_y: uint256 = math.get_y_D(amp, i, xp, D1, N_COINS) xp_reduced: DynArray[uint256, MAX_COINS] = xp - ys: uint256 = (D0 + D1) / (2 * N_COINS) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + # ys: uint256 = (D0 + D1) / (2 * N_COINS) + ys: uint256 = unsafe_div((D0 + D1), unsafe_mul(2, N_COINS)) + # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + base_fee: uint256 = unsafe_div(unsafe_mul(self.fee, N_COINS), 4) dx_expected: uint256 = 0 xp_j: uint256 = 0 @@ -1326,7 +1292,7 @@ def _calc_withdraw_one_coin( xp_j = xp[j] if j == i: dx_expected = xp_j * D1 / D0 - new_y - xavg = (xp_j + new_y) / 2 + xavg = unsafe_div(xp_j + new_y, 2) else: dx_expected = xp_j - xp_j * D1 / D0 xavg = xp_j @@ -1416,11 +1382,10 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # ---------------------------- Upkeep D oracle --------------------------- - last_D_packed_current: uint256 = self.last_D_packed self.last_D_packed = self.pack_2( D, self._calc_moving_average( - last_D_packed_current, + self.last_D_packed, self.D_ma_time, ma_last_time_unpacked[1], # index 1 is ma_exp_time for D ) @@ -1675,6 +1640,15 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @view @external def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Swap involves base pool tokens (either i or j should be 0); + If not, this method reverts. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ return StableSwapViews(factory.views_implementation()).get_dx_underlying(i, j, dy, self) @@ -1695,6 +1669,15 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @view @external def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Swap involves base pool tokens (either i or j should be 0); + If not, this method reverts. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ return StableSwapViews(factory.views_implementation()).get_dy_underlying(i, j, dx, self) diff --git a/poetry.lock b/poetry.lock index 297ebbb3..4b82f300 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "3201bf318d10c238071365be833ef8e3c7eaaef2" -resolved_reference = "3201bf318d10c238071365be833ef8e3c7eaaef2" +reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" +resolved_reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4bd0c70b9ebc5d3a0d92e626ea31898516bb129fd5f2ca0abaee77e4fd77565f" +content-hash = "40670953f2e928ef535bfedc35d1c0ea4fafd22fff5e96d6123780ba2b92b3cb" diff --git a/pyproject.toml b/pyproject.toml index 0898b544..24b897d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "3201bf318d10c238071365be833ef8e3c7eaaef2"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7171aee25c4d25fc1626a361a8c972e9316fd383"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 589f556d..d6ff3124 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -15,7 +15,7 @@ "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", - "meta_amm": "0xdd7ebb1c49780519dd9755b8b1a23a6f42ce099e", + "meta_amm": "", "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", }, @@ -23,20 +23,13 @@ "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", - "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - }, - "polygon:mainnet": { - "math": "", - "views": "", - "plain_amm": "", "meta_amm": "", - "factory": "", + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "arbitrum:mainnet": { - "math": "", - "views": "", - "plain_amm": "", + "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", + "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", + "plain_amm": "0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D", "meta_amm": "", "factory": "", }, @@ -54,42 +47,7 @@ "meta_amm": "", "factory": "", }, - "avax:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, - "aurora:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, - "celo:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, - "fantom:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, - "kava:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, - "moonbeam:mainnet": { + "polygon:mainnet": { "math": "", "views": "", "plain_amm": "", @@ -108,12 +66,15 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: if is_shanghai_chain and "# pragma evm-version paris" in source: logger.log("Replacing EVM version to Shanghai ...") - source = source.replace("# pragma evm-version paris\n", "# pragma evm-version shanghai\n") + new_source = source.replace("# pragma evm-version paris\n", "# pragma evm-version shanghai\n") elif not is_shanghai_chain and "# pragma evm-version shanghai" in source: logger.log("Replacing EVM version to Paris ...") - source = source.replace("# pragma evm-version shanghai\n", "# pragma evm-version paris\n") + new_source = source.replace("# pragma evm-version shanghai\n", "# pragma evm-version paris\n") + else: # all looks good ... + new_source = source - return boa.loads_partial(source_code=source) + contract_obj = boa.loads_partial(source_code=new_source) + return contract_obj def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): @@ -170,6 +131,7 @@ def deploy_infra(network, url, account, fork=False): logger.log("Deploying blueprints ...") plain_blueprint = check_and_deploy(plain_contract_obj, "plain_amm", network, blueprint=True) meta_blueprint = check_and_deploy(meta_contract_obj, "meta_amm", network, blueprint=True) + breakpoint() # Factory: factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) @@ -212,10 +174,10 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - ":mainnet", - "", + "arbitrum:mainnet", + "https://arb-mainnet.g.alchemy.com/v2/jx__wvt4TxgRHDiRwi9MaUk0Tmq1htPT", "FIDDYDEPLOYER", - fork=False, + fork=True, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 4bc9e357..d501cb0c 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -85,44 +85,24 @@ class CurveNetworkSettings: fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", ), "arbitrum:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", + dao_ownership_contract="0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", # proxy fee_receiver_address="0xd4f94d0aaa640bbb72b5eec2d85f6d114d81a88e", ), "optimism:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0xbF7E49483881C76487b0989CD7d9A8239B20CA41", ), "polygon:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0x774D1Dba98cfBD1F2Bc3A1F59c494125e07C48F9", ), - "avalanche:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xbabe61887f1de2713c6f97e567623453d3c79f67", - fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", + "base:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xe8269B33E47761f552E1a3070119560d5fa8bBD6", # proxy + fee_receiver_address="0xe8269B33E47761f552E1a3070119560d5fa8bBD6", # proxy ), "gnosis:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- need to deploy sidechain ownership contract # noqa: E501 - fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- need to deploy sidechain pool proxy - ), - "fantom:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # <--- thin proxy # noqa: E501 - fee_receiver_address="0x2B039565B2b7a1A9192D4847fbd33B25b836B950", - ), - "celo:mainnet": CurveNetworkSettings( - dao_ownership_contract="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", # <-- needs to accept transfer ownership for 0x5277A0226d10392295E8D383E9724D6E416d6e6C # noqa: E501 - fee_receiver_address="", - ), - "kava:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), - "moonbeam": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), - "aurora": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy + fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy ), } diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 76369467..1e62c44d 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -1,5 +1,3 @@ -import itertools - import boa import pytest @@ -130,39 +128,6 @@ def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving): swap.exchange_received(sending, receiving, 1, 0, bob) -@pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.skip_rebasing_tokens -@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_received_nonrebasing( - bob, - swap, - transfer_and_swap, - underlying_tokens, - sending, - receiving, -): - swap_data = transfer_and_swap(swap, sending, receiving, True) - - assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] - assert swap_data["bob"]["receiving_token"][1] - swap_data["bob"]["receiving_token"][0] == swap_data["amount_out"] - - # sending token swap balances should go up for sending_token_pool - # (could be base pool could be metapool): - sending_token_pool = swap_data["sending_token_pool"] - receiving_token_pool = swap_data["receiving_token_pool"] - assert ( - swap_data[sending_token_pool]["sending_token"][1] - swap_data[sending_token_pool]["sending_token"][0] - == swap_data["amount_in"] - ) - - # receiving token swap balances should go down for receiving_token_pool - # (could be base pool could be metapool): - assert ( - swap_data[receiving_token_pool]["receiving_token"][0] - swap_data[receiving_token_pool]["receiving_token"][1] - == swap_data["amount_out"] - ) - - @pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, receiving, transfer_and_swap): @@ -174,44 +139,6 @@ def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, rece transfer_and_swap(swap, sending, receiving, False) -@pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.skip_rebasing_tokens -@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_underlying_received_no_dos( - bob, charlie, swap, underlying_tokens, sending, receiving, transfer_and_swap -): - - if sending == 0: - input_coin = underlying_tokens[0] - else: - base_i = sending - 1 - input_coin = underlying_tokens[2 + base_i] - - mint_for_testing(bob, 1, input_coin, False) - input_coin.transfer(swap, 1, sender=bob) - - mint_for_testing(charlie, 10**18, input_coin, False) - transfer_and_swap(swap, sending, receiving, True) - - -@pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.skip_rebasing_tokens -@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_not_received(bob, swap, sending, receiving): - with boa.env.prank(bob), boa.reverts(): - swap.exchange_underlying_received(sending, receiving, 1, 0, bob) - - -@pytest.mark.only_for_pool_type(1) # only for metapools -@pytest.mark.contains_rebasing_tokens -@pytest.mark.parametrize("sending,receiving", list(itertools.combinations([0, 1, 2, 3], 2))) -def test_exchange_underlying_received_rebasing_reverts(swap, transfer_and_swap, sending, receiving): - - if 2 in swap._immutables.asset_types: - with boa.reverts(): - transfer_and_swap(swap, sending, receiving, True) - - @pytest.mark.contains_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): From 4aa954cc90b41c942c068d90e566a078dad9f742 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:06:09 +0200 Subject: [PATCH 213/337] add optimism deployment --- README.MD | 25 ++++++++ scripts/deploy_infra.py | 131 +++++++++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 22 deletions(-) diff --git a/README.MD b/README.MD index 41f07d76..89d3be43 100644 --- a/README.MD +++ b/README.MD @@ -15,6 +15,15 @@ For integrators: check exchange_received. That should improve your pathing signi 5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) 6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xfb37b8D939FFa77114005e61CFc2e543d6F49A81](https://sepolia.etherscan.io/address/0xfb37b8D939FFa77114005e61CFc2e543d6F49A81#code) +## Ethereum + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [](https://sepolia.etherscan.io/address/#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [](https://sepolia.etherscan.io/address/#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [](https://sepolia.etherscan.io/address/#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://sepolia.etherscan.io/address/#code) +5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [](https://sepolia.etherscan.io/address/#code) +6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [](https://sepolia.etherscan.io/address/#code) + ## Gnosis 1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://gnosisscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) @@ -23,6 +32,22 @@ For integrators: check exchange_received. That should improve your pathing signi 4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://gnosisscan.io/address/#code) 5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8](https://gnosisscan.io/address/0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8#code) +## Arbitrum + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a](https://arbiscan.io/address/0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0xC1b393EfEF38140662b91441C6710Aa704973228](https://arbiscan.io/address/0xC1b393EfEF38140662b91441C6710Aa704973228#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D](https://arbiscan.io/address/0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0xd125E7a0cEddF89c6473412d85835450897be6Dc](https://arbiscan.io/address/0xd125E7a0cEddF89c6473412d85835450897be6Dc#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x9AF14D26075f142eb3F292D5065EB3faa646167b](https://arbiscan.io/address/0x9AF14D26075f142eb3F292D5065EB3faa646167b#code) + +## Optimism + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6](https://optimistic.etherscan.io/address/0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://optimistic.etherscan.io/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://optimistic.etherscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://optimistic.etherscan.io/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://optimistic.etherscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) + ## Overview The metapool factory has several core components: diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index d6ff3124..4af7e4a5 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -15,7 +15,7 @@ "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", - "meta_amm": "", + "meta_amm": "0xa12A87c73718a34CD8601b5022B2C6C359142585", "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", }, @@ -23,22 +23,22 @@ "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "", + "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "arbitrum:mainnet": { "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", "plain_amm": "0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D", - "meta_amm": "", - "factory": "", + "meta_amm": "0xd125E7a0cEddF89c6473412d85835450897be6Dc", + "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { "math": "", @@ -85,6 +85,9 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo logger.log(f"Deploying {contract_designation} contract ...") if not blueprint: contract = contract_obj.deploy(*args) + if args: + constructor_args = encode(["address", "address"], args) + logger.log(f"Constructor arguments for {contract_designation}: {constructor_args.hex()}") else: contract = contract_obj.deploy_as_blueprint() logger.log(f"Deployed! At: {contract.address}.") @@ -97,9 +100,13 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo def deploy_infra(network, url, account, fork=False): + logger.log(f"Deploying on {network} ...") + if fork: boa.env.fork(url) + logger.log("Forkmode ...") else: + logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) for _network, data in deploy_utils.curve_dao_network_settings.items(): @@ -114,8 +121,6 @@ def deploy_infra(network, url, account, fork=False): # --------------------- Deploy math, views, blueprints --------------------- - logger.log("Setting EVM versions ...") - # get source and set evm_version math_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGMath.vy", network) views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) @@ -123,27 +128,19 @@ def deploy_infra(network, url, account, fork=False): meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) # deploy non-blueprint contracts: - logger.log("Deploying non-blueprint AMM components ...") math_contract = check_and_deploy(math_contract_obj, "math", network) views_contract = check_and_deploy(views_contract_obj, "views", network) # deploy blueprints: - logger.log("Deploying blueprints ...") plain_blueprint = check_and_deploy(plain_contract_obj, "plain_amm", network, blueprint=True) meta_blueprint = check_and_deploy(meta_contract_obj, "meta_amm", network, blueprint=True) - breakpoint() # Factory: factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) args = [fee_receiver, deploy_utils.FIDDYDEPLOYER] factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) - constructor_args = encode(["address", "address"], args) - logger.log(f"Constructor arguments for factory: {constructor_args.hex()}") - # Set up AMM implementations: - logger.log("Integrating AMM components into factory ...") - if not factory.views_implementation() == views_contract.address: factory.set_views_implementation(views_contract.address) logger.log(f"Set views implementation to: {views_contract.address}") @@ -173,13 +170,103 @@ def deploy_infra(network, url, account, fork=False): def main(): + + # # gnosis + # deploy_infra( + # "gnosis:mainnet", + # os.environ["RPC_GNOSIS"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # ethereum sepolia + # deploy_infra( + # "ethereum:sepolia", + # os.environ["RPC_ETHEREUM_SEPOLIA"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # arbitrum + # deploy_infra( + # "arbitrum:mainnet", + # os.environ["RPC_ARBITRUM"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # optimism + # deploy_infra( + # "optimism:mainnet", + # os.environ["RPC_OPTIMISM"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # polygon deploy_infra( - "arbitrum:mainnet", - "https://arb-mainnet.g.alchemy.com/v2/jx__wvt4TxgRHDiRwi9MaUk0Tmq1htPT", + "polygon:mainnet", + os.environ["RPC_POLYGON"], "FIDDYDEPLOYER", - fork=True, + fork=False, ) + # # base + # deploy_infra( + # "base:mainnet", + # os.environ["RPC_BASE"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # avax + # deploy_infra( + # "avax:mainnet", + # os.environ["RPC_AVAX"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # fantom + # deploy_infra( + # "fantom:mainnet", + # os.environ["RPC_FANTOM"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # kava + # deploy_infra( + # "kava:mainnet", + # os.environ["RPC_KAVA"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # celo + # deploy_infra( + # "celo:mainnet", + # os.environ["RPC_CELO"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # aurora + # deploy_infra( + # "aurora:mainnet", + # os.environ["RPC_AURORA"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # eth + # deploy_infra( + # "ethereum:mainnet", + # os.environ["RPC_ETHEREUM"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + if __name__ == "__main__": main() From 8b76e5781aee8f819f98bee42f618a33baf3903b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:03:38 +0200 Subject: [PATCH 214/337] polygon deployment! --- scripts/deploy_infra.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 4af7e4a5..891c12bc 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -40,14 +40,14 @@ "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, - "base:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, "polygon:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "base:mainnet": { "math": "", "views": "", "plain_amm": "", From 7a9a26c033096cfbfaa644e6d6faea4a0f4413e7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:05:38 +0200 Subject: [PATCH 215/337] add deployment addresses for polygon --- README.MD | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.MD b/README.MD index 89d3be43..70ffeebb 100644 --- a/README.MD +++ b/README.MD @@ -48,6 +48,14 @@ For integrators: check exchange_received. That should improve your pathing signi 4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://optimistic.etherscan.io/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) 5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://optimistic.etherscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) +## Polygon + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0xf3A431008396df8A8b2DF492C913706BDB0874ef](https://polygonscan.com/address/0xf3A431008396df8A8b2DF492C913706BDB0874ef#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6](https://polygonscan.com/address/0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://polygonscan.com/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://polygonscan.com/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://polygonscan.com/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) + ## Overview The metapool factory has several core components: From d564a9f43ef33062b2de3ee95a710fc167067aa9 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:03:54 +0200 Subject: [PATCH 216/337] base deployment --- README.MD | 8 ++++++++ scripts/deploy_infra.py | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.MD b/README.MD index 70ffeebb..156db871 100644 --- a/README.MD +++ b/README.MD @@ -56,6 +56,14 @@ For integrators: check exchange_received. That should improve your pathing signi 4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://polygonscan.com/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) 5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://polygonscan.com/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) +## Base + +1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://basescan.org/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) +2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://basescan.org/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) +3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://basescan.org/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) +4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x5eee3091f747e60a045a2e715a4c71e600e31f6e](https://basescan.org/address/0x5eee3091f747e60a045a2e715a4c71e600e31f6e#code) +5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://basescan.org/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) + ## Overview The metapool factory has several core components: diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 891c12bc..cd5bf77e 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -48,11 +48,11 @@ "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "base:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, } @@ -204,21 +204,21 @@ def main(): # ) # # polygon - deploy_infra( - "polygon:mainnet", - os.environ["RPC_POLYGON"], - "FIDDYDEPLOYER", - fork=False, - ) - - # # base # deploy_infra( - # "base:mainnet", - # os.environ["RPC_BASE"], + # "polygon:mainnet", + # os.environ["RPC_POLYGON"], # "FIDDYDEPLOYER", # fork=False, # ) + # # base + deploy_infra( + "base:mainnet", + os.environ["RPC_BASE"], + "FIDDYDEPLOYER", + fork=False, + ) + # # avax # deploy_infra( # "avax:mainnet", From 5926e9b7d34a99055eebe025de1981df3148f5eb Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:31:48 +0200 Subject: [PATCH 217/337] another round of deployments --- contracts/ProxyAdmin.vy | 156 ++++++++++++++++++++++++++++++++++ scripts/deploy_infra.py | 92 ++++++++++++++++++-- scripts/deploy_proxy_admin.py | 46 ++++++++++ scripts/deployment_utils.py | 41 +++++++++ tests/fixtures/accounts.py | 7 +- 5 files changed, 331 insertions(+), 11 deletions(-) create mode 100644 contracts/ProxyAdmin.vy create mode 100644 scripts/deploy_proxy_admin.py diff --git a/contracts/ProxyAdmin.vy b/contracts/ProxyAdmin.vy new file mode 100644 index 00000000..60c20297 --- /dev/null +++ b/contracts/ProxyAdmin.vy @@ -0,0 +1,156 @@ +# pragma version 0.3.10 +# pragma evm-version paris +""" +@title ProxyAdmin +@notice Thin proxy allowing shared ownership of contracts +@author Ben Hauser +@license MIT +""" + + +event TransactionExecuted: + admin: indexed(address) + target: indexed(address) + calldata: Bytes[100000] + value: uint256 + +event RequestAdminChange: + current_admin: address + future_admin: address + +event RevokeAdminChange: + current_admin: address + future_admin: address + calling_admin: address + +event ApproveAdminChange: + current_admin: address + future_admin: address + calling_admin: address + +event AcceptAdminChange: + previous_admin: address + current_admin: address + + +admins: public(address[2]) + +pending_current_admin: uint256 +pending_new_admin: address +change_approved: bool + + +@external +def __init__(_authorized: address[2]): + """ + @notice Contract constructor + @param _authorized Admin accounts for this contract + """ + self.admins = _authorized + + +@payable +@external +def execute(_target: address, _calldata: Bytes[100000]): + """ + @notice Execute a contract call + @dev Ether sent when calling this function is forwarded onward + @param _target Address of the contract to call + @param _calldata Calldata to use in the call + """ + assert msg.sender in self.admins # dev: only admin + + raw_call(_target, _calldata, value=msg.value) + log TransactionExecuted(msg.sender, _target, _calldata, msg.value) + + +@view +@external +def get_admin_change_status() -> (address, address, bool): + """ + @notice Get information about a pending admin change + @return Admin address to be replaced, + admin address to be added, + has change been approved? + """ + idx: uint256 = self.pending_current_admin + if idx == 0: + return ZERO_ADDRESS, ZERO_ADDRESS, False + else: + return self.admins[idx - 1], self.pending_new_admin, self.change_approved + + +@external +def request_admin_change(_new_admin: address): + """ + @notice Initiate changing an admin address + @param _new_admin New admin address (replaces msg.sender) + """ + assert self.pending_current_admin == 0 # dev: already an active request + + admin_list: address[2] = self.admins + assert _new_admin not in admin_list # dev: new admin is already admin + + for i in range(2): + if admin_list[i] == msg.sender: + self.pending_current_admin = i + 1 + self.pending_new_admin = _new_admin + log RequestAdminChange(msg.sender, _new_admin) + return + + raise # dev: only admin + + +@external +def approve_admin_change(): + """ + @notice Approve changing an admin address + @dev Only callable by the 2nd admin address (the one that will not change) + """ + idx: uint256 = self.pending_current_admin + + assert idx > 0 # dev: no active request + assert msg.sender == self.admins[idx % 2] # dev: caller is not 2nd admin + + self.change_approved = True + log ApproveAdminChange(self.admins[idx - 1], self.pending_new_admin, msg.sender) + + +@external +def revoke_admin_change(): + """ + @notice Revoke changing an admin address + @dev May be called by either admin at any time to reset the process, + even if approval has previous been given + """ + assert msg.sender in self.admins # dev: only admin + + idx: uint256 = self.pending_current_admin + pending_admin: address = ZERO_ADDRESS + if idx > 0: + pending_admin = self.admins[idx - 1] + + log RevokeAdminChange(pending_admin, self.pending_new_admin, msg.sender) + + self.pending_current_admin = 0 + self.pending_new_admin = ZERO_ADDRESS + self.change_approved = False + + + +@external +def accept_admin_change(): + """ + @notice Accept a changed admin address + @dev Only callable by the new admin address, after approval has been given + """ + assert self.change_approved == True # dev: change not approved + assert msg.sender == self.pending_new_admin # dev: only new admin + + idx: uint256 = self.pending_current_admin - 1 + log AcceptAdminChange(self.admins[idx], msg.sender) + self.admins[idx] = msg.sender + + self.pending_current_admin = 0 + self.pending_new_admin = ZERO_ADDRESS + self.change_approved = False diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index cd5bf77e..05b6fcd1 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -54,6 +54,76 @@ "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, + "avax:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "ftm:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, + "kava:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "celo:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "aurora:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "bsc:mainnet": { + "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", + "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + }, + "linea:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "scroll:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "zksync:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, + "pzkevm:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", + }, } @@ -212,12 +282,12 @@ def main(): # ) # # base - deploy_infra( - "base:mainnet", - os.environ["RPC_BASE"], - "FIDDYDEPLOYER", - fork=False, - ) + # deploy_infra( + # "base:mainnet", + # os.environ["RPC_BASE"], + # "FIDDYDEPLOYER", + # fork=False, + # ) # # avax # deploy_infra( @@ -229,7 +299,7 @@ def main(): # # fantom # deploy_infra( - # "fantom:mainnet", + # "ftm:mainnet", # os.environ["RPC_FANTOM"], # "FIDDYDEPLOYER", # fork=False, @@ -259,6 +329,14 @@ def main(): # fork=False, # ) + # # bsc + deploy_infra( + "bsc:mainnet", + os.environ["RPC_BSC"], + "FIDDYDEPLOYER", + fork=False, + ) + # # eth # deploy_infra( # "ethereum:mainnet", diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py new file mode 100644 index 00000000..9acbd5a1 --- /dev/null +++ b/scripts/deploy_proxy_admin.py @@ -0,0 +1,46 @@ +import os +import sys + +import boa +import deployment_utils as deploy_utils +from boa.network import NetworkEnv +from deploy_infra import set_evm_version +from eth_abi import encode +from eth_account import Account +from rich.console import Console as RichConsole + +logger = RichConsole(file=sys.stdout) + + +def deploy_proxy_admin(network, url, account, fork=False): + + logger.log(f"Deploying ProxyAdmin for {network} ...") + + if fork: + boa.env.fork(url) + logger.log("Forkmode ...") + else: + logger.log("Prodmode ...") + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + + # deploy thin proxy if no owners exist: + proxy_admin_contract_obj = set_evm_version("./contracts/ProxyAdmin.vy", network) + args = [deploy_utils.FIDDYDEPLOYER, deploy_utils.BABE] + encoded_args = encode(["address", "address"], args).hex() + logger.log(f"Constructor: {encoded_args}") + proxy_admin = proxy_admin_contract_obj.deploy(args) + logger.log(f"Deployed ProxyAdmin at {proxy_admin.address} on {network}.") + + +def main(): + deploy_proxy_admin( + "aurora:mainnet", + os.environ["RPC_AURORA"], + "FIDDYDEPLOYER", + fork=False, + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index d501cb0c..1d0d5fe1 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -53,6 +53,7 @@ def deploy_blueprint(contract, account): ADDRESS_PROVIDER = "0x0000000022d53366457f9d5e68ec105046fc4383" FIDDYRESEARCH = "0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17" FIDDYDEPLOYER = "0x2d12D0907A388811e3AA855A550F959501d303EE" +BABE = "0xbabe61887f1de2713c6f97e567623453d3C79f67" # -------------- CURVE DATA -------------- @@ -104,6 +105,46 @@ class CurveNetworkSettings: dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy ), + "avax:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy + fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", + ), + "ftm:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy + fee_receiver_address="0x2B039565B2b7a1A9192D4847fbd33B25b836B950", + ), + "kava:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x1f0e8445Ebe0D0F60A96A7cd5BB095533cb15B58", + fee_receiver_address="0x1f0e8445Ebe0D0F60A96A7cd5BB095533cb15B58", + ), + "celo:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", + fee_receiver_address="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", + ), + "aurora:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + "bsc:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", + fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", + ), + "linea:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + "scroll:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + "zksync:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + "pzkevm:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), } diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 45c8aa1c..63e9f4c8 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -2,7 +2,6 @@ import boa import pytest -from boa.environment import AddressType from eth_account.account import Account, LocalAccount from tests.utils.tokens import mint_for_testing @@ -11,17 +10,17 @@ @pytest.fixture(scope="module") -def deployer() -> AddressType: +def deployer(): return boa.env.generate_address() @pytest.fixture(scope="module") -def owner() -> AddressType: +def owner(): return boa.env.generate_address() @pytest.fixture(scope="module") -def fee_receiver() -> AddressType: +def fee_receiver(): return boa.env.generate_address() From 4a52715100ce0b839c9805306421e5c3061a6c98 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:34:09 +0200 Subject: [PATCH 218/337] more deployments --- scripts/deploy_infra.py | 22 +++++++++++++++------- scripts/deploy_proxy_admin.py | 4 ++-- scripts/deployment_utils.py | 4 ++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 05b6fcd1..b9d796ef 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -97,11 +97,11 @@ "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "linea:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { "math": "", @@ -330,9 +330,17 @@ def main(): # ) # # bsc + # deploy_infra( + # "bsc:mainnet", + # os.environ["RPC_BSC"], + # "FIDDYDEPLOYER", + # fork=False, + # ) + + # # linea deploy_infra( - "bsc:mainnet", - os.environ["RPC_BSC"], + "linea:mainnet", + os.environ["RPC_LINEA"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 9acbd5a1..631908df 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -35,8 +35,8 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "aurora:mainnet", - os.environ["RPC_AURORA"], + "linea:mainnet", + os.environ["RPC_LINEA"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 1d0d5fe1..7d709d3c 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -130,8 +130,8 @@ class CurveNetworkSettings: fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), "linea:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "scroll:mainnet": CurveNetworkSettings( dao_ownership_contract="", From eb1b6417fc628eaf56a736bb6705007a08b38a84 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:54:35 +0200 Subject: [PATCH 219/337] awaiting boa static fee integration --- scripts/deploy_infra.py | 212 ++++++++++------------------------ scripts/deploy_proxy_admin.py | 4 +- scripts/deployment_utils.py | 59 +++++----- 3 files changed, 95 insertions(+), 180 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index b9d796ef..84baa337 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -11,6 +11,7 @@ logger = RichConsole(file=sys.stdout) deployments = { + # Ethereum "ethereum:sepolia": { "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", @@ -19,13 +20,15 @@ "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", }, - "gnosis:mainnet": { - "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", - "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "ethereum:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "gauge": "", + "factory": "", }, + # Layer 2 "arbitrum:mainnet": { "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", @@ -40,13 +43,6 @@ "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, - "polygon:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, "base:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", @@ -54,41 +50,63 @@ "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, - "avax:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "ftm:mainnet": { + "linea:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", - "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, - "kava:mainnet": { + "scroll:mainnet": { "math": "", "views": "", "plain_amm": "", "meta_amm": "", "factory": "", }, - "celo:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zksync:mainnet": { + "math": "", + "views": "", + "plain_amm": "", + "meta_amm": "", + "factory": "", }, - "aurora:mainnet": { + "pzkevm:mainnet": { "math": "", "views": "", "plain_amm": "", "meta_amm": "", "factory": "", }, + # Layer 1 + "gnosis:mainnet": { + "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + }, + "polygon:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "avax:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "ftm:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, "bsc:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", @@ -96,28 +114,28 @@ "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, - "linea:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "celo:mainnet": { + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, - "scroll:mainnet": { + "kava:mainnet": { "math": "", "views": "", "plain_amm": "", "meta_amm": "", "factory": "", }, - "zksync:mainnet": { + "aurora:mainnet": { "math": "", "views": "", "plain_amm": "", "meta_amm": "", "factory": "", }, - "pzkevm:mainnet": { + "tron:mainnet": { "math": "", "views": "", "plain_amm": "", @@ -241,118 +259,14 @@ def deploy_infra(network, url, account, fork=False): def main(): - # # gnosis - # deploy_infra( - # "gnosis:mainnet", - # os.environ["RPC_GNOSIS"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # ethereum sepolia - # deploy_infra( - # "ethereum:sepolia", - # os.environ["RPC_ETHEREUM_SEPOLIA"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # arbitrum - # deploy_infra( - # "arbitrum:mainnet", - # os.environ["RPC_ARBITRUM"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # optimism - # deploy_infra( - # "optimism:mainnet", - # os.environ["RPC_OPTIMISM"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # polygon - # deploy_infra( - # "polygon:mainnet", - # os.environ["RPC_POLYGON"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # base - # deploy_infra( - # "base:mainnet", - # os.environ["RPC_BASE"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # avax - # deploy_infra( - # "avax:mainnet", - # os.environ["RPC_AVAX"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # fantom - # deploy_infra( - # "ftm:mainnet", - # os.environ["RPC_FANTOM"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # kava - # deploy_infra( - # "kava:mainnet", - # os.environ["RPC_KAVA"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # celo - # deploy_infra( - # "celo:mainnet", - # os.environ["RPC_CELO"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # aurora - # deploy_infra( - # "aurora:mainnet", - # os.environ["RPC_AURORA"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # bsc - # deploy_infra( - # "bsc:mainnet", - # os.environ["RPC_BSC"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - - # # linea + # eth deploy_infra( - "linea:mainnet", - os.environ["RPC_LINEA"], + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False, ) - # # eth - # deploy_infra( - # "ethereum:mainnet", - # os.environ["RPC_ETHEREUM"], - # "FIDDYDEPLOYER", - # fork=False, - # ) - if __name__ == "__main__": main() diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 631908df..69263731 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -35,8 +35,8 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "linea:mainnet", - os.environ["RPC_LINEA"], + "pzkevm:mainnet", + os.environ["RPC_PZKEVM"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 7d709d3c..d7eb5e90 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -69,22 +69,18 @@ class CurveNetworkSettings: curve_dao_network_settings = { - "ethereum:mainnet": CurveNetworkSettings( - dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", - fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", - metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", - base_pool_registry_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", + # Ethereum + "ethereum:sepolia": CurveNetworkSettings( + dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", + fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", ), - "ethereum:mainnet-fork": CurveNetworkSettings( + "ethereum:mainnet": CurveNetworkSettings( dao_ownership_contract="0x40907540d8a6C65c637785e8f8B742ae6b0b9968", fee_receiver_address="0xeCb456EA5365865EbAb8a2661B0c503410e9B347", metaregistry_address="0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC", base_pool_registry_address="0xDE3eAD9B2145bBA2EB74007e58ED07308716B725", ), - "ethereum:sepolia": CurveNetworkSettings( - dao_ownership_contract="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", - fee_receiver_address="0xE6DA683076b7eD6ce7eC972f21Eb8F91e9137a17", - ), + # Layer 2 "arbitrum:mainnet": CurveNetworkSettings( dao_ownership_contract="0xb055ebbacc8eefc166c169e9ce2886d0406ab49b", # proxy fee_receiver_address="0xd4f94d0aaa640bbb72b5eec2d85f6d114d81a88e", @@ -93,14 +89,31 @@ class CurveNetworkSettings: dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0xbF7E49483881C76487b0989CD7d9A8239B20CA41", ), - "polygon:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy - fee_receiver_address="0x774D1Dba98cfBD1F2Bc3A1F59c494125e07C48F9", - ), "base:mainnet": CurveNetworkSettings( dao_ownership_contract="0xe8269B33E47761f552E1a3070119560d5fa8bBD6", # proxy fee_receiver_address="0xe8269B33E47761f552E1a3070119560d5fa8bBD6", # proxy ), + "linea:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + ), + "scroll:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + "zksync:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1", + ), + "pzkevm:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), + # Layer 1 + "polygon:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy + fee_receiver_address="0x774D1Dba98cfBD1F2Bc3A1F59c494125e07C48F9", + ), "gnosis:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy @@ -129,21 +142,9 @@ class CurveNetworkSettings: dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), - "linea:mainnet": CurveNetworkSettings( - dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", - fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", - ), - "scroll:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), - "zksync:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), - "pzkevm:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + "tron:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", + fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), } From 01f5bbc612862f15337ed0679a8703f210308b01 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:52:39 +0200 Subject: [PATCH 220/337] add kava deployment --- poetry.lock | 6 +++--- pyproject.toml | 2 +- scripts/deploy_infra.py | 15 +++++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4b82f300..d843782c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" -resolved_reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" +reference = "7fcca390a0c4d94fe78e19221df618d264a609f1" +resolved_reference = "7fcca390a0c4d94fe78e19221df618d264a609f1" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "40670953f2e928ef535bfedc35d1c0ea4fafd22fff5e96d6123780ba2b92b3cb" +content-hash = "8c4d5d89084264c79558162dda8bf51fd649a3a3a78c7bb54f28996fda12fa45" diff --git a/pyproject.toml b/pyproject.toml index 24b897d9..a792ab1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7171aee25c4d25fc1626a361a8c972e9316fd383"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7fcca390a0c4d94fe78e19221df618d264a609f1"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 84baa337..f911e840 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -122,11 +122,11 @@ "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", + "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { "math": "", @@ -259,10 +259,9 @@ def deploy_infra(network, url, account, fork=False): def main(): - # eth deploy_infra( - "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], + "kava:mainnet", + os.environ["RPC_KAVA"], "FIDDYDEPLOYER", fork=False, ) From 23bb58368edbd75afccd8c98c65e2fc7128878af Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:43:42 +0200 Subject: [PATCH 221/337] deployed on aurora --- scripts/deploy_infra.py | 14 +++++++------- scripts/deploy_proxy_admin.py | 4 ++-- scripts/deployment_utils.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index f911e840..8f3659b4 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -129,11 +129,11 @@ "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "tron:mainnet": { "math": "", @@ -260,8 +260,8 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - "kava:mainnet", - os.environ["RPC_KAVA"], + "aurora:mainnet", + os.environ["RPC_AURORA"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 69263731..9acbd5a1 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -35,8 +35,8 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "pzkevm:mainnet", - os.environ["RPC_PZKEVM"], + "aurora:mainnet", + os.environ["RPC_AURORA"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index d7eb5e90..e8b90bc1 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -135,8 +135,8 @@ class CurveNetworkSettings: fee_receiver_address="0x56bc95Ded2BEF162131905dfd600F2b9F1B380a4", ), "aurora:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "bsc:mainnet": CurveNetworkSettings( dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", From 2eeb0d4023e93b5c2daaaf95cf8f7e12eb4448ff Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:31:20 +0200 Subject: [PATCH 222/337] deployed: polygon zkevm, scroll, ethereum! --- scripts/deploy_infra.py | 38 +++++++++++++++++------------------ scripts/deploy_proxy_admin.py | 4 ++-- scripts/deployment_utils.py | 12 +++++------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 8f3659b4..f63427ff 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -17,16 +17,16 @@ "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", "meta_amm": "0xa12A87c73718a34CD8601b5022B2C6C359142585", - "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", + "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", }, "ethereum:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "gauge": "", - "factory": "", + "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", + "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", + "plain_amm": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", + "meta_amm": "0x19bd1AB34d6ABB584b9C1D5519093bfAA7f6c7d2", + "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", + "gauge": "0xF5617D4f7514bE35fce829a1C19AE7f6c9106979", }, # Layer 2 "arbitrum:mainnet": { @@ -58,11 +58,11 @@ "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "zksync:mainnet": { "math": "", @@ -72,11 +72,11 @@ "factory": "", }, "pzkevm:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "views": "0x87fe17697d0f14a222e8bef386a0860ecffdd617", + "plain_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", + "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { @@ -260,8 +260,8 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - "aurora:mainnet", - os.environ["RPC_AURORA"], + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 9acbd5a1..69263731 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -35,8 +35,8 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "aurora:mainnet", - os.environ["RPC_AURORA"], + "pzkevm:mainnet", + os.environ["RPC_PZKEVM"], "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index e8b90bc1..cfbc7006 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -98,16 +98,16 @@ class CurveNetworkSettings: fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "scroll:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", # proxy + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "zksync:mainnet": CurveNetworkSettings( dao_ownership_contract="", fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1", ), "pzkevm:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + fee_receiver_address="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", ), # Layer 1 "polygon:mainnet": CurveNetworkSettings( @@ -143,8 +143,8 @@ class CurveNetworkSettings: fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), "tron:mainnet": CurveNetworkSettings( - dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", - fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", + dao_ownership_contract="", + fee_receiver_address="", ), } From fd229527269fa57fe4cf6920edcf8e5a461cddf2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:30:04 +0200 Subject: [PATCH 223/337] add base pools to factory --- poetry.lock | 6 +- pyproject.toml | 2 +- scripts/deployment_utils.py | 77 ---------------- scripts/set_up_base_pools.py | 168 +++++++++++++++++++++++++++++++++-- 4 files changed, 163 insertions(+), 90 deletions(-) diff --git a/poetry.lock b/poetry.lock index d843782c..79c9e2cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "7fcca390a0c4d94fe78e19221df618d264a609f1" -resolved_reference = "7fcca390a0c4d94fe78e19221df618d264a609f1" +reference = "1924f4888e2ef261efb2367fce2cf467de16f529" +resolved_reference = "1924f4888e2ef261efb2367fce2cf467de16f529" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8c4d5d89084264c79558162dda8bf51fd649a3a3a78c7bb54f28996fda12fa45" +content-hash = "4bfbf4bf7da0af835da1d07294f58a9bf4b5de283abb2957e2a2ae24d625726a" diff --git a/pyproject.toml b/pyproject.toml index a792ab1b..8c34534c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7fcca390a0c4d94fe78e19221df618d264a609f1"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "1924f4888e2ef261efb2367fce2cf467de16f529"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index cfbc7006..2dd1ac0b 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -156,83 +156,6 @@ class CurveNetworkSettings: "quorum": 30, } -# ------------- BASE POOLS --------------- - - -@dataclass -class BasePoolSettings: - pool: Address - lp_token: Address - coins: List[Address] - asset_types: List[int] - n_coins: int - - -_base_pool_list = { - "ethereum:mainnet": [ - BasePoolSettings( # 3pool - pool="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", - lp_token="0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490", - coins=[ - "0x6B175474E89094C44Da98b954EedeAC495271d0F", # dai - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc - "0xdAC17F958D2ee523a2206206994597C13D831ec7", # usdt - ], - asset_types=[0, 0, 0], - n_coins=3, - ), - BasePoolSettings( # fraxusdc - pool="0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2", - lp_token="0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC", - coins=[ - "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc - ], - asset_types=[0, 0], - n_coins=2, - ), - BasePoolSettings( # sbtc/wbtc - pool="0xf253f83AcA21aAbD2A20553AE0BF7F65C755A07F", - lp_token="0x051d7e5609917Bd9b73f04BAc0DED8Dd46a74301", - coins=[ - "0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6", # sbtc - "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", # wbtc - ], - asset_types=[0, 0], - n_coins=2, - ), - BasePoolSettings( - pool="0xaE34574AC03A15cd58A92DC79De7B1A0800F1CE3", - lp_token="0xFC2838a17D8e8B1D5456E0a351B0708a09211147", - coins=[ - "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax - "0x8E870D67F660D95d5be530380D0eC0bd388289E1", # usdp - ], - asset_types=[0, 0], - n_coins=2, - ), - ], - "arbitrum:mainnet": [], - "optimism:mainnet": [], - "gnosis:mainnet": [ - BasePoolSettings( - pool="0x7f90122bf0700f9e7e1f688fe926940e8839f353", - lp_token="0x1337BedC9D22ecbe766dF105c9623922A27963EC", - coins=[ - "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", - "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", - "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", - ], - asset_types=[0, 0, 0], - n_coins=3, - ), - ], - "polygon:mainnet": [], - "base:mainnet": [], -} -_base_pool_list["ethereum:mainnet-fork"] = _base_pool_list["ethereum:mainnet"] -base_pool_list = _base_pool_list - # -------------------------- POOL SETUP -------------------------- diff --git a/scripts/set_up_base_pools.py b/scripts/set_up_base_pools.py index 3f477e34..06d127ed 100644 --- a/scripts/set_up_base_pools.py +++ b/scripts/set_up_base_pools.py @@ -1,25 +1,151 @@ import os import sys +from dataclasses import dataclass +from typing import List import boa from boa.network import NetworkEnv from deploy_infra import deployments -from deployment_utils import base_pool_list +from deployment_utils import FIDDYDEPLOYER from eth_account import Account +from eth_typing import Address +from eth_utils import to_checksum_address from rich.console import Console as RichConsole logger = RichConsole(file=sys.stdout) -def set_up_base_pools(network, url, account, factory, fork: bool = False): +# ------------- BASE POOLS --------------- + +@dataclass +class BasePoolSettings: + pool: Address + lp_token: Address + coins: List[Address] + asset_types: List[int] + n_coins: int + + +base_pool_list = { + "ethereum:mainnet": [ + BasePoolSettings( # 3pool + pool="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", + lp_token="0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490", + coins=[ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", # dai + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc + "0xdAC17F958D2ee523a2206206994597C13D831ec7", # usdt + ], + asset_types=[0, 0, 0], + n_coins=3, + ), + BasePoolSettings( # fraxusdc + pool="0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2", + lp_token="0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC", + coins=[ + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # usdc + ], + asset_types=[0, 0], + n_coins=2, + ), + BasePoolSettings( # sbtc/wbtc + pool="0xf253f83AcA21aAbD2A20553AE0BF7F65C755A07F", + lp_token="0x051d7e5609917Bd9b73f04BAc0DED8Dd46a74301", + coins=[ + "0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6", # sbtc + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", # wbtc + ], + asset_types=[0, 0], + n_coins=2, + ), + BasePoolSettings( + pool="0xaE34574AC03A15cd58A92DC79De7B1A0800F1CE3", + lp_token="0xFC2838a17D8e8B1D5456E0a351B0708a09211147", + coins=[ + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0x8E870D67F660D95d5be530380D0eC0bd388289E1", # usdp + ], + asset_types=[0, 0], + n_coins=2, + ), + ], + "arbitrum:mainnet": [ + BasePoolSettings( # 2pool + pool="0x7f90122BF0700F9E7e1F688fe926940E8839F353", + lp_token="0x7f90122BF0700F9E7e1F688fe926940E8839F353", + coins=[ + "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + ], + asset_types=[0, 0], + n_coins=2, + ), + BasePoolSettings( # fraxbp + pool="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", + lp_token="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", + coins=[ + "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + ], + asset_types=[0, 0], + n_coins=2, + ), + ], + "optimism:mainnet": [ + BasePoolSettings( # 3pool + pool="0x1337BedC9D22ecbe766dF105c9623922A27963EC", + lp_token="0x1337BedC9D22ecbe766dF105c9623922A27963EC", + coins=[ + "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", + ], + asset_types=[0, 0, 0], + n_coins=3, + ), + BasePoolSettings( # fraxbp + pool="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", + lp_token="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", + coins=[ + "0x2E3D870790dC77A83DD1d18184Acc7439A53f475", + "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + ], + asset_types=[0, 0], + n_coins=2, + ), + ], + "gnosis:mainnet": [ + BasePoolSettings( + pool="0x7f90122bf0700f9e7e1f688fe926940e8839f353", + lp_token="0x1337BedC9D22ecbe766dF105c9623922A27963EC", + coins=[ + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", + "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", + ], + asset_types=[0, 0, 0], + n_coins=3, + ), + ], +} + + +def set_up_base_pools(network, url, account, fork: bool = False): + + logger.log(f"Connecting to {network} ...") if fork: boa.env.fork(url) + boa.env.eoa = FIDDYDEPLOYER + logger.log("Forkmode") else: + logger.log("Prodmode") boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) - factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy").at(factory) + factory_address = deployments[network]["factory"] + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy").at(factory_address) logger.log("Setting up base pools ...") base_pool_data = base_pool_list[network] @@ -27,25 +153,49 @@ def set_up_base_pools(network, url, account, factory, fork: bool = False): if base_pool_data: # check if network has base pools: for data in base_pool_data: - if data.pool not in onboarded_base_pools: + if to_checksum_address(data.pool) not in onboarded_base_pools: factory.add_base_pool( data.pool, data.lp_token, data.asset_types, data.n_coins, ) - logger.log(f"Added {data.pool} to factory {factory.address} on {network}.") + logger.log(f"Added {data.pool} to factory {factory_address} on {network}.") + else: + logger.log(f"{data.pool} is already configured as a base pool in factory {factory_address}.") + + assert factory.base_pool_data(data.pool)[0] == data.lp_token - assert factory.base_pool_data(data.pool)[0] == data.pool + logger.log(f"Base pools set up for {network}!") def main(): + + fork = False + + set_up_base_pools( + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], + "FIDDYDEPLOYER", + fork=fork, + ) + set_up_base_pools( + "arbitrum:mainnet", + os.environ["RPC_ARBITRUM"], + "FIDDYDEPLOYER", + fork=fork, + ) + set_up_base_pools( + "optimism:mainnet", + os.environ["RPC_OPTIMISM"], + "FIDDYDEPLOYER", + fork=fork, + ) set_up_base_pools( "gnosis:mainnet", - "https://gnosis.drpc.org", + os.environ["RPC_GNOSIS"], "FIDDYDEPLOYER", - deployments["gnosis:mainnet"]["factory"], - False, # forkmode + fork=fork, ) From 61f497f6315c31d33d8002b5c4827f20d9188055 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:32:40 +0100 Subject: [PATCH 224/337] add boa scripts and remove ape script --- scripts/deploy.py | 188 ---------------------------------- scripts/deploy_pool.py | 132 +++++++++++++++++++++++- scripts/deploy_proxy_admin.py | 7 +- scripts/deployment_utils.py | 60 +---------- 4 files changed, 136 insertions(+), 251 deletions(-) delete mode 100644 scripts/deploy.py diff --git a/scripts/deploy.py b/scripts/deploy.py deleted file mode 100644 index 231796c2..00000000 --- a/scripts/deploy.py +++ /dev/null @@ -1,188 +0,0 @@ -import click -from ape import Contract, accounts, project -from ape.cli import NetworkBoundCommand, account_option, network_option -from ape.logging import logger - -import scripts.deployment_utils as deploy_utils - - -@click.group(short_help="Deploy the project") -def cli(): - pass - - -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -def deploy_infra(network, account): - - if account.alias == "fiddydeployer": - account.set_autosign(True) - - for _network, data in deploy_utils.curve_dao_network_settings.items(): - - if _network in network: - - owner = data.dao_ownership_contract - fee_receiver = data.fee_receiver_address - - assert owner, f"Curve's DAO contracts may not be on {network}." - assert fee_receiver, f"Curve's DAO contracts may not be on {network}." - - with accounts.use_sender(account): - - # --------------------- Deploy math, views, blueprints --------------------- - - logger.info("Deploying AMM components ...") - math_contract = account.deploy( - project.CurveStableSwapNGMath, - **deploy_utils._get_tx_params(), - ) - views_contract = account.deploy( - project.CurveStableSwapNGViews, - **deploy_utils._get_tx_params(), - ) - plain_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapNG, account) - meta_blueprint_contract = deploy_utils.deploy_blueprint(project.CurveStableSwapMetaNG, account) - gauge_blueprint_contract = deploy_utils.deploy_blueprint(project.LiquidityGauge, account) - - # --------------------- DEPLOY FACTORY --------------------------- - - logger.info("Deploying factory ...") - factory = account.deploy( - project.CurveStableSwapFactoryNG, - deploy_utils.curve_dao_network_settings[network].fee_receiver_address, # fee_receiver - account, # owner (temporary) - ) - - logger.info("Integrating AMM components into factory ...") - - factory.set_gauge_implementation(gauge_blueprint_contract, **deploy_utils._get_tx_params()) - factory.set_views_implementation(views_contract, **deploy_utils._get_tx_params()) - factory.set_math_implementation(math_contract, **deploy_utils._get_tx_params()) - - factory.set_pool_implementations(0, plain_blueprint_contract, **deploy_utils._get_tx_params()) - factory.set_metapool_implementations(0, meta_blueprint_contract, **deploy_utils._get_tx_params()) - - -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -@click.option("--factory", required=True, type=str) -def set_up_base_pools(network, account, factory): - - logger.info("Setting up base pools ...") - base_pool_data = deploy_utils.base_pool_list[network] - if base_pool_data: # check if network has base pools: - for data in base_pool_data: - factory.add_base_pool( - data.pool, - data.lp_token, - data.coins, - data.asset_types, - data.n_coins, - **deploy_utils._get_tx_params(), - ) - logger.info(f"Added {data.pool} to factory") - - -@cli.command(cls=NetworkBoundCommand) -@network_option() -@account_option() -@click.option("--factory", required=True, type=str) -def set_up_registries(network, account, factory): - - if account.alias == "fiddydeployer": - account.set_autosign(True) - - for _network, data in deploy_utils.curve_dao_network_settings.items(): - - if _network in network: - - owner = data.dao_ownership_contract - fee_receiver = data.fee_receiver_address - address_provider = Contract(data.address_provider) - - assert owner, f"Curve's DAO contracts may not be on {network}." - assert fee_receiver, f"Curve's DAO contracts may not be on {network}." - - with accounts.use_sender(account): - - # -------------------------- Register into AddressProvider -------------------------- - - max_id = address_provider.max_id() - description = "Curve StableSwapNG" - boss = Contract(address_provider.admin()) - - # check if account can handle boss: - account_is_boss_handler = False - for i in range(2): - if account.address.lower() == boss.admins(i).lower(): - account_is_boss_handler = True - break - - assert account_is_boss_handler # only authorised accounts can write to address provider # noqa: E501 - - for index in range(max_id + 1): - if address_provider.get_id_info(index).description is description: - break - - if index == max_id: - - logger.info(f"Adding a new registry provider entry at id: {index + 1}") - - # we're adding a new id - with accounts.use_sender(account) as account: - boss.execute( - address_provider.address, - address_provider.add_new_id.encode_input(factory, description), - gas_limit=400000, - **deploy_utils._get_tx_params(), - ) - - else: - - assert address_provider.get_id_info(index).description == description - - logger.info(f"Updating existing registry provider entry at id: {index}") - - # we're updating an existing id with the same description: - with accounts.use_sender(account) as account: - boss.execute( - address_provider.address, - address_provider.set_address.encode_input(index, factory), - gas_limit=200000, - **deploy_utils._get_tx_params(), - ) - - assert address_provider.get_id_info(index).addr.lower() == factory.lower() - - logger.info("AddressProvider integration complete!") - - # -------------------------- Set up metaregistry -------------------------- - - metaregistry_address = deploy_utils.curve_dao_network_settings[network].metaregistry_address - base_pool_registry_address = deploy_utils.curve_dao_network_settings[network].base_pool_registry_address - - if metaregistry_address: - - metaregistry = Contract(metaregistry_address) - boss = Contract(metaregistry.owner()) - - # set up metaregistry integration: - logger.info("Integrate into Metaregistry ...") - logger.info("Deploying Factory handler to integrate it to the metaregistry:") - factory_handler = account.deploy( - project.CurveStableSwapFactoryNGHandler, - factory.address, - base_pool_registry_address, - **deploy_utils._get_tx_params(), - ) - - boss.execute( - metaregistry.address, - metaregistry.add_registry_handler.encode_input(factory_handler), - **deploy_utils._get_tx_params(), - ) - - logger.info("Metaregistry integration complete!") diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 93355b6a..342e249c 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -1,21 +1,144 @@ import os import sys +from dataclasses import dataclass +from typing import List import boa from boa.network import NetworkEnv -from deploy_infra import deployments -from deployment_utils import pool_settings from eth_account import Account +from eth_typing import Address from rich.console import Console as RichConsole logger = RichConsole(file=sys.stdout) +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" +deployments = { + # Ethereum + "ethereum:sepolia": { + "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", + }, + "ethereum:mainnet": { + "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", + }, + # Layer 2 + "arbitrum:mainnet": { + "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", + }, + "optimism:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "base:mainnet": { + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + }, + "linea:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "scroll:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "zksync:mainnet": { + "factory": "", + }, + "pzkevm:mainnet": { + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + }, + "mantle:mainnet": {"factory": ""}, + # Layer 1 + "gnosis:mainnet": { + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + }, + "polygon:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "avax:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "ftm:mainnet": { + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, + "bsc:mainnet": { + "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + }, + "celo:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "kava:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "aurora:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "tron:mainnet": { + "factory": "", + }, +} -def deploy_pool(network, url, account, fork, pool_type): + +# -------------------------- POOL SETUP -------------------------- + + +@dataclass +class PoolSettings: + name: str + symbol: str + coins: List[Address] + A: int + fee: int + offpeg_fee_multiplier: int + ma_exp_time: int + implementation_idx: int + asset_types: List[int] + method_ids: List[int] + oracles: List[Address] + + +pool_settings = { + "gnosis:mainnet": { + "plain": [ + "WXDAI/USDC/USDT", # name + "3pool-ng", # symbol + [ + "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", # wxdai + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", # usdc + "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", # usdt + ], + 1000, # A + 1000000, # fee + 20000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + [0, 0, 0], # asset_types + [b"", b"", b""], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles + ], + "meta": [ + "0x7f90122bf0700f9e7e1f688fe926940e8839f353", # base_pool + "EURE/3CRV", # name + "eure3crvng", # symbol + "0xcb444e90d8198415266c6a2724b7900fb12fc56e", # eure + 500, # A + 1000000, # fee + 20000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + 0, # asset_types + b"", # method_ids + ZERO_ADDRESS, # oracles + ], + } +} + + +def deploy_pool(network, url, account, pool_type, fork): + + logger.log(f"Deploying pool on {network} ...") if fork: boa.env.fork(url) + logger.log("Forkmode ...") + boa.env.eoa = "" # set eoa address here else: + logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) @@ -33,7 +156,8 @@ def deploy_pool(network, url, account, fork, pool_type): def main(): - deploy_pool("gnosis:mainnet", "https://gnosis.drpc.org", "FIDDYDEPLOYER", False, "meta") # forkmode + fork = True + deploy_pool("gnosis:mainnet", "https://gnosis.drpc.org", "FIDDYDEPLOYER", "meta", fork) if __name__ == "__main__": diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 69263731..e65e8ae3 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -19,6 +19,7 @@ def deploy_proxy_admin(network, url, account, fork=False): if fork: boa.env.fork(url) logger.log("Forkmode ...") + boa.env.eoa = deploy_utils.FIDDYDEPLOYER else: logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) @@ -35,10 +36,10 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "pzkevm:mainnet", - os.environ["RPC_PZKEVM"], + "mantle:mainnet", + os.environ["RPC_MANTLE"], "FIDDYDEPLOYER", - fork=False, + fork=True, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 2dd1ac0b..3570375b 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import List import click from ape import networks, project @@ -8,7 +7,6 @@ # from eth_utils import function_signature_to_4byte_selector DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 -ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" def _get_tx_params(): @@ -146,6 +144,10 @@ class CurveNetworkSettings: dao_ownership_contract="", fee_receiver_address="", ), + "mantle:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), } @@ -155,57 +157,3 @@ class CurveNetworkSettings: "token": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2", "quorum": 30, } - -# -------------------------- POOL SETUP -------------------------- - - -@dataclass -class PoolSettings: - name: str - symbol: str - coins: List[Address] - A: int - fee: int - offpeg_fee_multiplier: int - ma_exp_time: int - implementation_idx: int - asset_types: List[int] - method_ids: List[int] - oracles: List[Address] - - -pool_settings = { - "gnosis:mainnet": { - "plain": [ - "WXDAI/USDC/USDT", # name - "3pool-ng", # symbol - [ - "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", # wxdai - "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", # usdc - "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", # usdt - ], - 1000, # A - 1000000, # fee - 20000000000, # offpeg_fee_multiplier - 865, # ma_exp_time - 0, # implementation index - [0, 0, 0], # asset_types - [b"", b"", b""], # method_ids - [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles - ], - "meta": [ - "0x7f90122bf0700f9e7e1f688fe926940e8839f353", # base_pool - "EURE/3CRV", # name - "eure3crvng", # symbol - "0xcb444e90d8198415266c6a2724b7900fb12fc56e", # eure - 500, # A - 1000000, # fee - 20000000000, # offpeg_fee_multiplier - 865, # ma_exp_time - 0, # implementation index - 0, # asset_types - b"", # method_ids - ZERO_ADDRESS, # oracles - ], - } -} From d82428f4d9ca6906ebe915b05238bd2e7b60f52b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:33:50 +0100 Subject: [PATCH 225/337] park workflows --- .github/{workflows => tentative_workflows}/test_factory.yaml | 0 .github/{workflows => tentative_workflows}/test_gauge.yaml | 0 .github/{workflows => tentative_workflows}/test_pools_2.yaml | 0 .github/{workflows => tentative_workflows}/test_token.yaml | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => tentative_workflows}/test_factory.yaml (100%) rename .github/{workflows => tentative_workflows}/test_gauge.yaml (100%) rename .github/{workflows => tentative_workflows}/test_pools_2.yaml (100%) rename .github/{workflows => tentative_workflows}/test_token.yaml (100%) diff --git a/.github/workflows/test_factory.yaml b/.github/tentative_workflows/test_factory.yaml similarity index 100% rename from .github/workflows/test_factory.yaml rename to .github/tentative_workflows/test_factory.yaml diff --git a/.github/workflows/test_gauge.yaml b/.github/tentative_workflows/test_gauge.yaml similarity index 100% rename from .github/workflows/test_gauge.yaml rename to .github/tentative_workflows/test_gauge.yaml diff --git a/.github/workflows/test_pools_2.yaml b/.github/tentative_workflows/test_pools_2.yaml similarity index 100% rename from .github/workflows/test_pools_2.yaml rename to .github/tentative_workflows/test_pools_2.yaml diff --git a/.github/workflows/test_token.yaml b/.github/tentative_workflows/test_token.yaml similarity index 100% rename from .github/workflows/test_token.yaml rename to .github/tentative_workflows/test_token.yaml From 183c16e19737f752f941ad6a7de82a5492b02a01 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:35:24 +0100 Subject: [PATCH 226/337] fix readme --- README.MD | 58 +------------------------------------------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/README.MD b/README.MD index 156db871..26b1ec4a 100644 --- a/README.MD +++ b/README.MD @@ -6,63 +6,7 @@ For integrators: check exchange_received. That should improve your pathing signi # Deployments -## Sepolia - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443](https://sepolia.etherscan.io/address/0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0](https://sepolia.etherscan.io/address/0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd](https://sepolia.etherscan.io/address/0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://sepolia.etherscan.io/address/#code) -5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [0x64891ab20392a029c0f231656ff13c5ee64b730c](https://sepolia.etherscan.io/address/0x64891ab20392a029c0f231656ff13c5ee64b730c#code) -6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xfb37b8D939FFa77114005e61CFc2e543d6F49A81](https://sepolia.etherscan.io/address/0xfb37b8D939FFa77114005e61CFc2e543d6F49A81#code) - -## Ethereum - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [](https://sepolia.etherscan.io/address/#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [](https://sepolia.etherscan.io/address/#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [](https://sepolia.etherscan.io/address/#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://sepolia.etherscan.io/address/#code) -5. [`LiquidityGauge`](contracts/main/LiquidityGauge.vy): [](https://sepolia.etherscan.io/address/#code) -6. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [](https://sepolia.etherscan.io/address/#code) - -## Gnosis - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://gnosisscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://gnosisscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://gnosisscan.io/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [](https://gnosisscan.io/address/#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8](https://gnosisscan.io/address/0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8#code) - -## Arbitrum - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a](https://arbiscan.io/address/0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0xC1b393EfEF38140662b91441C6710Aa704973228](https://arbiscan.io/address/0xC1b393EfEF38140662b91441C6710Aa704973228#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D](https://arbiscan.io/address/0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0xd125E7a0cEddF89c6473412d85835450897be6Dc](https://arbiscan.io/address/0xd125E7a0cEddF89c6473412d85835450897be6Dc#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x9AF14D26075f142eb3F292D5065EB3faa646167b](https://arbiscan.io/address/0x9AF14D26075f142eb3F292D5065EB3faa646167b#code) - -## Optimism - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6](https://optimistic.etherscan.io/address/0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://optimistic.etherscan.io/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://optimistic.etherscan.io/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://optimistic.etherscan.io/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](https://optimistic.etherscan.io/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E#code) - -## Polygon - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0xf3A431008396df8A8b2DF492C913706BDB0874ef](https://polygonscan.com/address/0xf3A431008396df8A8b2DF492C913706BDB0874ef#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6](https://polygonscan.com/address/0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://polygonscan.com/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://polygonscan.com/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://polygonscan.com/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) - -## Base - -1. [`CurveStableSwapNGMath`](contracts/main/CurveStableSwapNGMath.vy): [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](https://basescan.org/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f#code) -2. [`CurveStableSwapNGViews`](contracts/main/CurveStableSwapNGViews.vy): [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](https://basescan.org/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617#code) -3. [`StableSwapNG`](contracts/main/CurveStableSwapNG.vy): [0x1764ee18e8B3ccA4787249Ceb249356192594585](https://basescan.org/address/0x1764ee18e8B3ccA4787249Ceb249356192594585#code) -4. [`StableSwapMetaNG`](contracts/main/CurveStableSwapMetaNG.vy): [0x5eee3091f747e60a045a2e715a4c71e600e31f6e](https://basescan.org/address/0x5eee3091f747e60a045a2e715a4c71e600e31f6e#code) -5. [`CurveStableSwapFactoryNG`](contracts/main/CurveStableSwapFactoryNG.vy): [0xd2002373543Ce3527023C75e7518C274A51ce712](https://basescan.org/address/0xd2002373543Ce3527023C75e7518C274A51ce712#code) +For a full list of deployments, please check: [The deployment script](scripts/deploy_infra.py) ## Overview From d3f056941f168c48dee72f1499f8757cc23a38f4 Mon Sep 17 00:00:00 2001 From: Martin Krung Date: Thu, 2 Nov 2023 10:52:46 +0100 Subject: [PATCH 227/337] Update README.MD Changed link to docs.curve.fi --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 26b1ec4a..bb547c48 100644 --- a/README.MD +++ b/README.MD @@ -15,7 +15,7 @@ The metapool factory has several core components: - [`Factory`](contracts/main/CurveStableSwapFactoryNG.vy) is the main contract used to deploy new metapools. It also acts a registry for finding the deployed pools and querying information about them. - New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwapNG.vy) targetted by the proxy is determined according to the base pool. -See the [documentation](https://curve.readthedocs.io) for more detailed information. +See the [documentation](https://docs.curve.fi) for more detailed information. ## Testing From d00b84a4a8f5aedb9f0dc9f706af3f1b3b9f642a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:36:18 +0100 Subject: [PATCH 228/337] introduce A_oracle in implementation --- contracts/main/CurveStableSwapNGAOracle.vy | 1844 ++++++++++++++++++++ 1 file changed, 1844 insertions(+) create mode 100644 contracts/main/CurveStableSwapNGAOracle.vy diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy new file mode 100644 index 00000000..fe3208ba --- /dev/null +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -0,0 +1,1844 @@ +# pragma version 0.3.10 +# pragma optimize codesize +# pragma evm-version shanghai +""" +@title CurveStableSwapNGAOracle +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Stableswap implementation for up to 8 coins with no rehypothecation, + i.e. the AMM does not deposit tokens into other contracts. The Pool contract also + records exponential moving averages for coins relative to coin 0. +@dev Asset Types: + 0. Standard ERC20 token with no additional features. + Note: Users are advised to do careful due-diligence on + ERC20 tokens that they interact with, as this + contract cannot differentiate between harmless and + malicious ERC20 tokens. + 1. Oracle - token with rate oracle (e.g. wstETH) + Note: Oracles may be controlled externally by an EOA. Users + are advised to proceed with caution. + 2. Rebasing - token with rebase (e.g. stETH). + Note: Users and Integrators are advised to understand how + the AMM contract works with rebasing balances. + 3. ERC4626 - token with convertToAssets method (e.g. sDAI). + Note: Some ERC4626 implementations may be susceptible to + Donation/Inflation attacks. Users are advised to + proceed with caution. + Supports: + 1. ERC20 support for return True/revert, return True/False, return None + 2. ERC20 tokens can have arbitrary decimals (<=18). + 3. ERC20 tokens that rebase (either positive or fee on transfer) + 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) + Note: Oracle precision _must_ be 10**18. + 5. ERC4626 tokens with arbitrary precision (<=18) of Vault token and underlying + asset. + Additional features include: + 1. Adds price oracles based on AMM State Price (and _not_ last traded price). + 2. Adds TVL oracle based on D. + 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred + prior to executing the swap. + Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) + then calling `exchange_received` will REVERT. + b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) + then this is an incorrect implementation and rebases can be + stolen. + 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output + of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected + input of coin[i] for an output amount of coin[j]. + 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. This can cause very + slight discrepancies between calculated fees and realised fees. + 6. The AMM contract queries Amplification factor from an external contract. This + external contract is set by the deployer of the pool. + Note: Users should do careful due diligence to ascertain that the pool owner address + is not malicious/buggy. +""" + +from vyper.interfaces import ERC20 +from vyper.interfaces import ERC20Detailed +from vyper.interfaces import ERC4626 + +implements: ERC20 + +# ------------------------------- Interfaces --------------------------------- + +interface Factory: + def fee_receiver() -> address: view + def admin() -> address: view + def views_implementation() -> address: view + +interface ERC1271: + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view + +interface StableSwapViews: + def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view + def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view + def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view + def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool, + _pool: address + ) -> uint256: view + +# --------------------------------- Events ----------------------------------- + +event Transfer: + sender: indexed(address) + receiver: indexed(address) + value: uint256 + +event Approval: + owner: indexed(address) + spender: indexed(address) + value: uint256 + +event TokenExchange: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event TokenExchangeUnderlying: + buyer: indexed(address) + sold_id: int128 + tokens_sold: uint256 + bought_id: int128 + tokens_bought: uint256 + +event AddLiquidity: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + invariant: uint256 + token_supply: uint256 + +event RemoveLiquidity: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + token_supply: uint256 + +event RemoveLiquidityOne: + provider: indexed(address) + token_id: int128 + token_amount: uint256 + coin_amount: uint256 + token_supply: uint256 + +event RemoveLiquidityImbalance: + provider: indexed(address) + token_amounts: DynArray[uint256, MAX_COINS] + fees: DynArray[uint256, MAX_COINS] + invariant: uint256 + token_supply: uint256 + +event ApplyNewFee: + fee: uint256 + offpeg_fee_multiplier: uint256 + +event SetNewAOracle: + oracle: address + + +MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory +MAX_COINS_128: constant(int128) = 8 + +# ---------------------------- Pool Variables -------------------------------- + +pool_manager: public(immutable(address)) + +N_COINS: public(immutable(uint256)) +N_COINS_128: immutable(int128) +PRECISION: constant(uint256) = 10 ** 18 + +factory: immutable(Factory) +coins: public(immutable(DynArray[address, MAX_COINS])) +asset_types: immutable(DynArray[uint8, MAX_COINS]) +stored_balances: DynArray[uint256, MAX_COINS] + +# Fee specific vars +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +fee: public(uint256) # fee * 1e10 +offpeg_fee_multiplier: public(uint256) # * 1e10 +admin_fee: public(constant(uint256)) = 5000000000 +MAX_FEE: constant(uint256) = 5 * 10 ** 9 + +# ---------------------- Pool Amplification Parameters ----------------------- + +A_PRECISION: constant(uint256) = 100 +MAX_A: constant(uint256) = 10 ** 6 + +# ---------------------------- Admin Variables ------------------------------- + +MIN_RAMP_TIME: constant(uint256) = 86400 +admin_balances: public(DynArray[uint256, MAX_COINS]) + +# ----------------------- Oracle Specific vars ------------------------------- + +rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) +# [bytes4 method_id][bytes8 ][bytes20 oracle] +oracles: DynArray[uint256, MAX_COINS] + +# For ERC4626 tokens, we need: +call_amount: immutable(DynArray[uint256, MAX_COINS]) +scale_factor: immutable(DynArray[uint256, MAX_COINS]) + +last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price +last_D_packed: uint256 # packing: last_D, ma_D +ma_exp_time: public(uint256) +D_ma_time: public(uint256) +ma_last_time: public(uint256) # packing: ma_last_time_p, ma_last_time_D +# ma_last_time has a distinction for p and D because p is _not_ updated if +# users remove_liquidity, but D is. + +# shift(2**32 - 1, 224) +ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 + +# --------------------------- ERC20 Specific Vars ---------------------------- + +name: public(immutable(String[64])) +symbol: public(immutable(String[32])) +decimals: public(constant(uint8)) = 18 +version: public(constant(String[8])) = "v7.0.0" + +balanceOf: public(HashMap[address, uint256]) +allowance: public(HashMap[address, HashMap[address, uint256]]) +total_supply: uint256 +nonces: public(HashMap[address, uint256]) + +# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 +ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 +EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") +EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + +VERSION_HASH: constant(bytes32) = keccak256(version) +NAME_HASH: immutable(bytes32) +CACHED_CHAIN_ID: immutable(uint256) +salt: public(immutable(bytes32)) +CACHED_DOMAIN_SEPARATOR: immutable(bytes32) + + +# ------------------------------ AMM Setup ----------------------------------- + + +@external +def __init__( + _name: String[32], + _symbol: String[10], + _A: uint256, + _fee: uint256, + _offpeg_fee_multiplier: uint256, + _ma_exp_time: uint256, + _coins: DynArray[address, MAX_COINS], + _rate_multipliers: DynArray[uint256, MAX_COINS], + _asset_types: DynArray[uint8, MAX_COINS], + _method_ids: DynArray[bytes4, MAX_COINS], + _oracles: DynArray[address, MAX_COINS], +): + """ + @notice Initialize the pool contract + @param _name Name of the new plain pool. + @param _symbol Symbol for the new plain pool. + @param _A Amplification co-efficient - a lower value here means + less tolerance for imbalance within the pool's assets. + Suggested values include: + * Uncollateralized algorithmic stablecoins: 5-10 + * Non-redeemable, collateralized assets: 100 + * Redeemable assets: 200-400 + @param _fee Trade fee, given as an integer with 1e10 precision. The + the maximum is 1% (100000000). + 50% of the fee is distributed to veCRV holders. + @param _offpeg_fee_multiplier A multiplier that determines how much to increase + Fees by when assets in the AMM depeg. Example value: 20000000000 + @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) + Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 + @param _coins List of addresses of the coins being used in the pool. + @param _rate_multipliers An array of: [10 ** (36 - _coins[n].decimals()), ... for n in range(N_COINS)] + @param _asset_types Array of uint8 representing tokens in pool + @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures + of the oracle addresses that gives rate oracles. + Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] + @param _oracles Array of rate oracle addresses. + """ + + pool_manager = tx.origin + + coins = _coins + asset_types = _asset_types + __n_coins: uint256 = len(_coins) + N_COINS = __n_coins + N_COINS_128 = convert(__n_coins, int128) + + rate_multipliers = _rate_multipliers + + factory = Factory(msg.sender) + + A: uint256 = _A * A_PRECISION + self.initial_A = A + self.future_A = A + self.fee = _fee + self.offpeg_fee_multiplier = _offpeg_fee_multiplier + + assert _ma_exp_time != 0 + self.ma_exp_time = _ma_exp_time + self.D_ma_time = 62324 # <--------- 12 hours default on contract start. + self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) + + # ------------------- initialize storage for DynArrays ------------------ + + _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if i < N_COINS_128 - 1: + self.last_prices_packed.append(self.pack_2(10**18, 10**18)) + + self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + self.stored_balances.append(0) + self.admin_balances.append(0) + + if _asset_types[i] == 3: + + _call_amount.append(10**convert(ERC20Detailed(_coins[i]).decimals(), uint256)) + _underlying_asset: address = ERC4626(_coins[i]).asset() + _scale_factor.append(10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256))) + + else: + + _call_amount.append(0) + _scale_factor.append(0) + + call_amount = _call_amount + scale_factor = _scale_factor + + # ----------------------------- ERC20 stuff ------------------------------ + + name = _name + symbol = _symbol + + # EIP712 related params ----------------- + NAME_HASH = keccak256(name) + salt = block.prevhash + CACHED_CHAIN_ID = chain.id + CACHED_DOMAIN_SEPARATOR = keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + + # ------------------------ Fire a transfer event ------------------------- + + log Transfer(empty(address), msg.sender, 0) + + +# ------------------ Token transfers in and out of the AMM ------------------- + + +@internal +def _transfer_in( + coin_idx: int128, + dx: uint256, + sender: address, + expect_optimistic_transfer: bool, +) -> uint256: + """ + @notice Contains all logic to handle ERC20 token transfers. + @param coin_idx Index of the coin to transfer in. + @param dx amount of `_coin` to transfer into the pool. + @param dy amount of `_coin` to transfer out of the pool. + @param sender address to transfer `_coin` from. + @param receiver address to transfer `_coin` to. + @param expect_optimistic_transfer True if contract expects an optimistic coin transfer + """ + _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) + + # ------------------------- Handle Transfers ----------------------------- + + if expect_optimistic_transfer: + + _dx = _dx - self.stored_balances[coin_idx] + assert _dx >= dx + + else: + + assert dx > 0 # dev : do not transferFrom 0 tokens into the pool + assert ERC20(coins[coin_idx]).transferFrom( + sender, self, dx, default_return_value=True + ) + + _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx + + # --------------------------- Store transferred in amount --------------------------- + + self.stored_balances[coin_idx] += _dx + + return _dx + + +@internal +def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): + """ + @notice Transfer a single token from the pool to receiver. + @dev This function is called by `remove_liquidity` and + `remove_liquidity_one`, `_exchange` and `_withdraw_admin_fees` methods. + @param _coin_idx Index of the token to transfer out + @param _amount Amount of token to transfer out + @param receiver Address to send the tokens to + """ + + coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + + # ------------------------- Handle Transfers ----------------------------- + + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + + # ----------------------- Update Stored Balances ------------------------- + + self.stored_balances[_coin_idx] = coin_balance - _amount + + +# -------------------------- AMM Special Methods ----------------------------- + + +@view +@internal +def _stored_rates() -> DynArray[uint256, MAX_COINS]: + """ + @notice Gets rate multipliers for each coin. + @dev If the coin has a rate oracle that has been properly initialised, + this method queries that rate by static-calling an external + contract. + """ + rates: DynArray[uint256, MAX_COINS] = rate_multipliers + oracles: DynArray[uint256, MAX_COINS] = self.oracles + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if asset_types[i] == 1 and not oracles[i] == 0: + + # NOTE: fetched_rate is assumed to be 10**18 precision + fetched_rate: uint256 = convert( + raw_call( + convert(oracles[i] % 2**160, address), + _abi_encode(oracles[i] & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ), + uint256 + ) + + rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) + + elif asset_types[i] == 3: # ERC4626 + + # fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i] + # here: call_amount has ERC4626 precision, but the returned value is scaled up to 18 + # using scale_factor which is (18 - n) if underlying asset has n decimals. + rates[i] = unsafe_div( + rates[i] * ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i], + PRECISION + ) # 1e18 precision + + return rates + + +@view +@internal +def _balances() -> DynArray[uint256, MAX_COINS]: + """ + @notice Calculates the pool's balances _excluding_ the admin's balances. + @dev If the pool contains rebasing tokens, this method ensures LPs keep all + rebases and admin only claims swap fees. This also means that, since + admin's balances are stored in an array and not inferred from read balances, + the fees in the rebasing token that the admin collects is immune to + slashing events. + """ + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances_i: uint256 = 0 + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if 2 in asset_types: + balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + else: + balances_i = self.stored_balances[i] - self.admin_balances[i] + + result.append(balances_i) + + return result + + +# -------------------------- AMM Main Functions ------------------------------ + + +@external +@nonreentrant('lock') +def exchange( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index value of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + return self._exchange( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + False + ) + + +@external +@nonreentrant('lock') +def exchange_received( + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Perform an exchange between two coins without transferring token in + @dev The contract swaps tokens based on a change in balance of coin[i]. The + dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of + this method are dex aggregators, arbitrageurs, or other users who do not + wish to grant approvals to the contract: they would instead send tokens + directly to the contract and call `exchange_received`. + Note: This is disabled if pool contains rebasing tokens. + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param _dx Amount of `i` being exchanged + @param _min_dy Minimum amount of `j` to receive + @return Actual amount of `j` received + """ + assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens + return self._exchange( + msg.sender, + i, + j, + _dx, + _min_dy, + _receiver, + True, # <--------------------------------------- swap optimistically. + ) + + +@external +@nonreentrant('lock') +def add_liquidity( + _amounts: DynArray[uint256, MAX_COINS], + _min_mint_amount: uint256, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Deposit coins into the pool + @param _amounts List of amounts of coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that owns the minted LP tokens + @return Amount of LP tokens received by depositing + """ + amp: uint256 = self._A() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + + # Initial invariant + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + + total_supply: uint256 = self.total_supply + new_balances: DynArray[uint256, MAX_COINS] = old_balances + + # -------------------------- Do Transfers In ----------------------------- + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if _amounts[i] > 0: + + new_balances[i] += self._transfer_in( + i, + _amounts[i], + msg.sender, + False, # expect_optimistic_transfer + ) + + else: + + assert total_supply != 0 # dev: initial deposit requires all coins + + # ------------------------------------------------------------------------ + + # Invariant after change + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + assert D1 > D0 + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + mint_amount: uint256 = 0 + + if total_supply > 0: + + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 + + ys: uint256 = (D0 + D1) / N_COINS + xs: uint256 = 0 + _dynamic_fee_i: uint256 = 0 + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + ideal_balance = D1 * old_balances[i] / D0 + difference = 0 + new_balance = new_balances[i] + + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + + # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) + fees.append(_dynamic_fee_i * difference / FEE_DENOMINATOR) + self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) + D1 = self.get_D(xp, amp) # <--------------- Reuse D1 for new D value. + mint_amount = total_supply * (D1 - D0) / D0 + self.upkeep_oracles(xp, amp, D1) + + else: + + mint_amount = D1 # Take the dust if there was any + + # (re)instantiate D oracle if totalSupply is zero. + self.last_D_packed = self.pack_2(D1, D1) + + assert mint_amount >= _min_mint_amount, "Slippage screwed you" + + # Mint pool tokens + total_supply += mint_amount + self.balanceOf[_receiver] += mint_amount + self.total_supply = total_supply + log Transfer(empty(address), _receiver, mint_amount) + + log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + + return mint_amount + + +@external +@nonreentrant('lock') +def remove_liquidity_one_coin( + _burn_amount: uint256, + i: int128, + _min_received: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Withdraw a single coin from the pool + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_received Minimum amount of coin to receive + @param _receiver Address that receives the withdrawn coins + @return Amount of coin received + """ + assert _burn_amount > 0 # dev: do not remove 0 LP tokens + dy: uint256 = 0 + fee: uint256 = 0 + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + amp: uint256 = empty(uint256) + D: uint256 = empty(uint256) + + dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i) + assert dy >= _min_received, "Not enough coins removed" + + self.admin_balances[i] += fee * admin_fee / FEE_DENOMINATOR + + self._burnFrom(msg.sender, _burn_amount) + + self._transfer_out(i, dy, _receiver) + + log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) + + self.upkeep_oracles(xp, amp, D) + + return dy + + +@external +@nonreentrant('lock') +def remove_liquidity_imbalance( + _amounts: DynArray[uint256, MAX_COINS], + _max_burn_amount: uint256, + _receiver: address = msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the withdrawn coins + @return Actual amount of the LP token burned in the withdrawal + """ + amp: uint256 = self._A() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + D0: uint256 = self.get_D_mem(rates, old_balances, amp) + new_balances: DynArray[uint256, MAX_COINS] = old_balances + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if _amounts[i] != 0: + new_balances[i] -= _amounts[i] + self._transfer_out(i, _amounts[i], _receiver) + + D1: uint256 = self.get_D_mem(rates, new_balances, amp) + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + ys: uint256 = (D0 + D1) / N_COINS + + fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + dynamic_fee: uint256 = 0 + xs: uint256 = 0 + ideal_balance: uint256 = 0 + difference: uint256 = 0 + new_balance: uint256 = 0 + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + ideal_balance = D1 * old_balances[i] / D0 + difference = 0 + new_balance = new_balances[i] + + if ideal_balance > new_balance: + difference = ideal_balance - new_balance + else: + difference = new_balance - ideal_balance + + xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) + dynamic_fee = self._dynamic_fee(xs, ys, base_fee) + fees.append(dynamic_fee * difference / FEE_DENOMINATOR) + + self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR + new_balances[i] -= fees[i] + + D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. + + self.upkeep_oracles(new_balances, amp, D1) + + total_supply: uint256 = self.total_supply + burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 + assert burn_amount > 1 # dev: zero tokens burned + assert burn_amount <= _max_burn_amount, "Slippage screwed you" + + total_supply -= burn_amount + self._burnFrom(msg.sender, burn_amount) + + log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + + return burn_amount + + +@external +@nonreentrant('lock') +def remove_liquidity( + _burn_amount: uint256, + _min_amounts: DynArray[uint256, MAX_COINS], + _receiver: address = msg.sender, + _claim_admin_fees: bool = True, +) -> DynArray[uint256, MAX_COINS]: + """ + @notice Withdraw coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the withdrawn coins + @return List of amounts of coins that were withdrawn + """ + total_supply: uint256 = self.total_supply + assert _burn_amount > 0 # dev: invalid burn amount + + amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + balances: DynArray[uint256, MAX_COINS] = self._balances() + + value: uint256 = 0 + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + value = balances[i] * _burn_amount / total_supply + assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" + amounts.append(value) + self._transfer_out(i, value, _receiver) + + self._burnFrom(msg.sender, _burn_amount) # <---- Updates self.total_supply + + # --------------------------- Upkeep D_oracle ---------------------------- + + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + last_D_packed_current: uint256 = self.last_D_packed + old_D: uint256 = last_D_packed_current & (2**128 - 1) + + self.last_D_packed = self.pack_2( + old_D - unsafe_div(old_D * _burn_amount, total_supply), # new_D = proportionally reduce D. + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1] + ) + ) + + if ma_last_time_unpacked[1] < block.timestamp: + ma_last_time_unpacked[1] = block.timestamp + + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + + # ------------------------------- Log event ------------------------------ + + log RemoveLiquidity( + msg.sender, + amounts, + empty(DynArray[uint256, MAX_COINS]), + total_supply - _burn_amount + ) + + # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- + if _claim_admin_fees: + self._withdraw_admin_fees() + + return amounts + + +@external +def withdraw_admin_fees(): + """ + @notice Claim admin fees. Callable by anyone. + """ + self._withdraw_admin_fees() + + +# ------------------------ AMM Internal Functions ---------------------------- + + +@view +@internal +def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: + + _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier + if _offpeg_fee_multiplier <= FEE_DENOMINATOR: + return _fee + + xps2: uint256 = (xpi + xpj) ** 2 + return ( + (_offpeg_fee_multiplier * _fee) / + ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + ) + + +@internal +def __exchange( + x: uint256, + _xp: DynArray[uint256, MAX_COINS], + rates: DynArray[uint256, MAX_COINS], + i: int128, + j: int128, +) -> uint256: + + amp: uint256 = self._A() + D: uint256 = self.get_D(_xp, amp) + y: uint256 = self.get_y(i, j, x, _xp, amp, D) + + dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors + dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR + + # Convert all to real units + dy = (dy - dy_fee) * PRECISION / rates[j] + + self.admin_balances[j] += ( + dy_fee * admin_fee / FEE_DENOMINATOR + ) * PRECISION / rates[j] + + # Calculate and store state prices: + xp: DynArray[uint256, MAX_COINS] = _xp + xp[i] = x + xp[j] = y + # D is not changed because we did not apply a fee + self.upkeep_oracles(xp, amp, D) + + return dy + + +@internal +def _exchange( + sender: address, + i: int128, + j: int128, + _dx: uint256, + _min_dy: uint256, + receiver: address, + expect_optimistic_transfer: bool +) -> uint256: + + assert i != j # dev: coin index out of range + assert _dx > 0 # dev: do not exchange 0 coins + + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: DynArray[uint256, MAX_COINS] = self._balances() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + + # --------------------------- Do Transfer in ----------------------------- + + # `dx` is whatever the pool received after ERC20 transfer: + dx: uint256 = self._transfer_in( + i, + _dx, + sender, + expect_optimistic_transfer + ) + + # ------------------------------- Exchange ------------------------------- + + x: uint256 = xp[i] + dx * rates[i] / PRECISION + dy: uint256 = self.__exchange(x, xp, rates, i, j) + assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" + + # --------------------------- Do Transfer out ---------------------------- + + self._transfer_out(j, dy, receiver) + + # ------------------------------------------------------------------------ + + log TokenExchange(msg.sender, i, _dx, j, dy) + + return dy + + +@internal +def _withdraw_admin_fees(): + fee_receiver: address = factory.fee_receiver() + assert fee_receiver != empty(address) # dev: fee receiver not set + + admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + if admin_balances[i] > 0: + + self._transfer_out(i, admin_balances[i], fee_receiver) + admin_balances[i] = 0 + + self.admin_balances = admin_balances + + +# --------------------------- AMM Math Functions ----------------------------- + + +@view +@internal +def get_y( + i: int128, + j: int128, + x: uint256, + xp: DynArray[uint256, MAX_COINS], + _amp: uint256, + _D: uint256 +) -> uint256: + """ + Calculate x[j] if one makes x[i] = x + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i != j # dev: same coin + assert j >= 0 # dev: j below zero + assert j < N_COINS_128 # dev: j above N_COINS + + # should be unreachable, but good for safety + assert i >= 0 + assert i < N_COINS_128 + + amp: uint256 = _amp + D: uint256 = _D + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = amp * N_COINS + + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + + if _i == i: + _x = x + elif _i != j: + _x = xp[_i] + else: + continue + + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann # - D + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@pure +@internal +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: + """ + D invariant calculation in non-overflowing integer operations + iteratively + + A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + + Converging solution: + D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) + """ + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + D_P: uint256 = 0 + Dprev: uint256 = 0 + + for i in range(255): + + D_P = D + for x in _xp: + D_P = D_P * D / (x * N_COINS) + Dprev = D + + # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + D = ( + (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * + D / ( + unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + + unsafe_add(N_COINS, 1) * D_P + ) + ) + + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise + + +@pure +@internal +def get_y_D( + A: uint256, + i: int128, + xp: DynArray[uint256, MAX_COINS], + D: uint256 +) -> uint256: + """ + Calculate x[i] if one reduces D from being calculated for xp to D + + Done by solving quadratic equation iteratively. + x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + x_1**2 + b*x_1 = c + + x_1 = (x_1**2 + c) / (2*x_1 + b) + """ + # x in the input is converted to the same price/precision + + assert i >= 0 # dev: i below zero + assert i < N_COINS_128 # dev: i above N_COINS + + S_: uint256 = 0 + _x: uint256 = 0 + y_prev: uint256 = 0 + c: uint256 = D + Ann: uint256 = A * N_COINS + + for _i in range(MAX_COINS_128): + + if _i == N_COINS_128: + break + + if _i != i: + _x = xp[_i] + else: + continue + S_ += _x + c = c * D / (_x * N_COINS) + + c = c * D * A_PRECISION / (Ann * N_COINS) + b: uint256 = S_ + D * A_PRECISION / Ann + y: uint256 = D + + for _i in range(255): + y_prev = y + y = (y*y + c) / (2 * y + b - D) + # Equality with the precision of 1 + if y > y_prev: + if y - y_prev <= 1: + return y + else: + if y_prev - y <= 1: + return y + raise + + +@view +@internal +def _A() -> uint256: + """ + @notice Queries an external contract for Amplification factor + @dev Since A is in an external contract, ramping A up or down is + a property of the external contract and not the AMM contract. + @return uint256 Fetched amplification factor + """ + A_oracle: uint256 = self.A_oracle + fetched_A: uint256 = convert( + raw_call( + convert(A_oracle % 2**160, address), + _abi_encode(A_oracle & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, + ), + uint256 + ) + + assert fetched_A < MAX_A # dev: fetched A is too high + + return fetched_A + + + +@pure +@internal +def _xp_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS] +) -> DynArray[uint256, MAX_COINS]: + + result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(MAX_COINS_128): + if i == N_COINS_128: + break + result.append(_rates[i] * _balances[i] / PRECISION) + return result + + +@view +@internal +def get_D_mem( + _rates: DynArray[uint256, MAX_COINS], + _balances: DynArray[uint256, MAX_COINS], + _amp: uint256 +) -> uint256: + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) + return self.get_D(xp, _amp) + + +@view +@internal +def _calc_withdraw_one_coin( + _burn_amount: uint256, + i: int128 +) -> ( + uint256, + uint256, + DynArray[uint256, MAX_COINS], + uint256, + uint256 +): + # First, need to calculate + # * Get current D + # * Solve Eqn against y_i for D - _token_amount + amp: uint256 = self._A() + rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) + D0: uint256 = self.get_D(xp, amp) + + total_supply: uint256 = self.total_supply + D1: uint256 = D0 - _burn_amount * D0 / total_supply + new_y: uint256 = self.get_y_D(amp, i, xp, D1) + + base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + ys: uint256 = (D0 + D1) / (2 * N_COINS) + xp_reduced: DynArray[uint256, MAX_COINS] = xp + + dx_expected: uint256 = 0 + xp_j: uint256 = 0 + xavg: uint256 = 0 + dynamic_fee: uint256 = 0 + + for j in range(MAX_COINS_128): + + if j == N_COINS_128: + break + + dx_expected = 0 + xp_j = xp[j] + + if j == i: + dx_expected = xp_j * D1 / D0 - new_y + xavg = (xp_j + new_y) / 2 + else: + dx_expected = xp_j - xp_j * D1 / D0 + xavg = xp_j + + dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) + xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR + + dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) + dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees + dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + + # update xp with new_y for p calculations. + xp[i] = new_y + + return dy, dy_0 - dy, xp, amp, D1 + + +# -------------------------- AMM Price Methods ------------------------------- + +@pure +@internal +def pack_2(p1: uint256, p2: uint256) -> uint256: + assert p1 < 2**128 + assert p2 < 2**128 + return p1 | (p2 << 128) + + +@pure +@internal +def unpack_2(packed: uint256) -> uint256[2]: + return [packed & (2**128 - 1), packed >> 128] + + +@internal +@pure +def _get_p( + xp: DynArray[uint256, MAX_COINS], + amp: uint256, + D: uint256, +) -> DynArray[uint256, MAX_COINS]: + + # dx_0 / dx_1 only, however can have any number of coins in pool + ANN: uint256 = unsafe_mul(amp, N_COINS) + Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) + + for i in range(MAX_COINS_128): + + if i == N_COINS_128: + break + + Dr = Dr * D / xp[i] + + p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp0_A: uint256 = ANN * xp[0] / A_PRECISION + + for i in range(1, MAX_COINS): + + if i == N_COINS: + break + + p.append(10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr)) + + return p + + +@internal +def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): + """ + @notice Upkeeps price and D oracles. + """ + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed + last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current + + spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D) + + # -------------------------- Upkeep price oracle ------------------------- + + for i in range(MAX_COINS): + + if i == N_COINS - 1: + break + + if spot_price[i] != 0: + + # Upate packed prices ----------------- + last_prices_packed_new[i] = self.pack_2( + spot_price[i], + self._calc_moving_average( + last_prices_packed_current[i], + self.ma_exp_time, + ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices + ) + ) + + self.last_prices_packed = last_prices_packed_new + + # ---------------------------- Upkeep D oracle --------------------------- + + last_D_packed_current: uint256 = self.last_D_packed + self.last_D_packed = self.pack_2( + D, + self._calc_moving_average( + last_D_packed_current, + self.D_ma_time, + ma_last_time_unpacked[1], # index 1 is ma_exp_time for D + ) + ) + + # Housekeeping: Update ma_last_time for p and D oracles ------------------ + for i in range(2): + if ma_last_time_unpacked[i] < block.timestamp: + ma_last_time_unpacked[i] = block.timestamp + + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + + +@internal +@view +def _calc_moving_average( + packed_value: uint256, + averaging_window: uint256, + ma_last_time: uint256 +) -> uint256: + + last_spot_value: uint256 = packed_value & (2**128 - 1) + last_ema_value: uint256 = (packed_value >> 128) + + if ma_last_time < block.timestamp: # calculate new_ema_value and return that. + alpha: uint256 = self.exp( + -convert( + (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 + ) + ) + return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 + + return last_ema_value + + +@view +@external +def last_price(i: uint256) -> uint256: + return self.last_prices_packed[i] & (2**128 - 1) + + +@view +@external +def ema_price(i: uint256) -> uint256: + return (self.last_prices_packed[i] >> 128) + + +@external +@view +def get_p(i: uint256) -> uint256: + """ + @notice Returns the AMM State price of token + @dev if i = 0, it will return the state price of coin[1]. + @param i index of state price (0 for coin[1], 1 for coin[2], ...) + @return uint256 The state price quoted by the AMM for coin[i+1] + """ + amp: uint256 = self._A() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) + D: uint256 = self.get_D(xp, amp) + return self._get_p(xp, amp, D)[i] + + +@external +@view +@nonreentrant('lock') +def price_oracle(i: uint256) -> uint256: + return self._calc_moving_average( + self.last_prices_packed[i], + self.ma_exp_time, + self.ma_last_time & (2**128 - 1) + ) + + +@external +@view +@nonreentrant('lock') +def D_oracle() -> uint256: + return self._calc_moving_average( + self.last_D_packed, + self.D_ma_time, + self.ma_last_time >> 128 + ) + + +# ----------------------------- Math Utils ----------------------------------- + + +@internal +@pure +def exp(x: int256) -> uint256: + """ + @dev Calculates the natural exponential function of a signed integer with + a precision of 1e18. + @notice Note that this function consumes about 810 gas units. The implementation + is inspired by Remco Bloemen's implementation under the MIT license here: + https://xn--2-umb.com/22/exp-ln. + @dev This implementation is derived from Snekmate, which is authored + by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. + https://github.com/pcaversaccio/snekmate + @param x The 32-byte variable. + @return int256 The 32-byte calculation result. + """ + value: int256 = x + + # If the result is `< 0.5`, we return zero. This happens when we have the following: + # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". + if (x <= -42139678854452767551): + return empty(uint256) + + # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. + # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". + assert x < 135305999368893231589, "wad_exp overflow" + + # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher + # intermediate precision and a binary base. This base conversion is a multiplication with + # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". + value = unsafe_div(x << 78, 5 ** 18) + + # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two + # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives + # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". + k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 + value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) + + # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, + # we will multiply by a scaling factor later. + y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) + p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ + 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) + + # We leave `p` in the "2 ** 192" base so that we do not have to scale it up + # again for the division. + q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) + q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) + q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) + q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) + + # The polynomial `q` has no zeros in the range because all its roots are complex. + # No scaling is required, as `p` is already "2 ** 96" too large. Also, + # `r` is in the range "(0.09, 0.25) * 2**96" after the division. + r: int256 = unsafe_div(p, q) + + # To finalise the calculation, we have to multiply `r` by: + # - the scale factor "s = ~6.031367120", + # - the factor "2 ** k" from the range reduction, and + # - the factor "1e18 / 2 ** 96" for the base conversion. + # We do this all at once, with an intermediate result in "2**213" base, + # so that the final right shift always gives a positive value. + + # Note that to circumvent Vyper's safecast feature for the potentially + # negative parameter value `r`, we first convert `r` to `bytes32` and + # subsequently to `uint256`. Remember that the EVM default behaviour is + # to use two's complement representation to handle signed integers. + return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) + + +# ---------------------------- ERC20 Utils ----------------------------------- + +@view +@internal +def _domain_separator() -> bytes32: + if chain.id != CACHED_CHAIN_ID: + return keccak256( + _abi_encode( + EIP712_TYPEHASH, + NAME_HASH, + VERSION_HASH, + chain.id, + self, + salt, + ) + ) + return CACHED_DOMAIN_SEPARATOR + + +@internal +def _transfer(_from: address, _to: address, _value: uint256): + # # NOTE: vyper does not allow underflows + # # so the following subtraction would revert on insufficient balance + self.balanceOf[_from] -= _value + self.balanceOf[_to] += _value + + log Transfer(_from, _to, _value) + + +@internal +def _burnFrom(_from: address, _burn_amount: uint256): + + self.total_supply -= _burn_amount + self.balanceOf[_from] -= _burn_amount + log Transfer(_from, empty(address), _burn_amount) + + +@external +def transfer(_to : address, _value : uint256) -> bool: + """ + @dev Transfer token for a specified address + @param _to The address to transfer to. + @param _value The amount to be transferred. + """ + self._transfer(msg.sender, _to, _value) + return True + + +@external +def transferFrom(_from : address, _to : address, _value : uint256) -> bool: + """ + @dev Transfer tokens from one address to another. + @param _from address The address which you want to send tokens from + @param _to address The address which you want to transfer to + @param _value uint256 the amount of tokens to be transferred + """ + self._transfer(_from, _to, _value) + + _allowance: uint256 = self.allowance[_from][msg.sender] + if _allowance != max_value(uint256): + self.allowance[_from][msg.sender] = _allowance - _value + + return True + + +@external +def approve(_spender : address, _value : uint256) -> bool: + """ + @notice Approve the passed address to transfer the specified amount of + tokens on behalf of msg.sender + @dev Beware that changing an allowance via this method brings the risk that + someone may use both the old and new allowance by unfortunate transaction + ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + @param _spender The address which will transfer the funds + @param _value The amount of tokens that may be transferred + @return bool success + """ + self.allowance[msg.sender][_spender] = _value + + log Approval(msg.sender, _spender, _value) + return True + + +@external +def permit( + _owner: address, + _spender: address, + _value: uint256, + _deadline: uint256, + _v: uint8, + _r: bytes32, + _s: bytes32 +) -> bool: + """ + @notice Approves spender by owner's signature to expend owner's tokens. + See https://eips.ethereum.org/EIPS/eip-2612. + @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 + @dev Supports smart contract wallets which implement ERC1271 + https://eips.ethereum.org/EIPS/eip-1271 + @param _owner The address which is a source of funds and has signed the Permit. + @param _spender The address which is allowed to spend the funds. + @param _value The amount of tokens to be spent. + @param _deadline The timestamp after which the Permit is no longer valid. + @param _v The bytes[64] of the valid secp256k1 signature of permit by owner + @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner + @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner + @return True, if transaction completes successfully + """ + assert _owner != empty(address) + assert block.timestamp <= _deadline + + nonce: uint256 = self.nonces[_owner] + digest: bytes32 = keccak256( + concat( + b"\x19\x01", + self._domain_separator(), + keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) + ) + ) + + if _owner.is_contract: + sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) + # reentrancy not a concern since this is a staticcall + assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL + else: + assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner + + self.allowance[_owner][_spender] = _value + self.nonces[_owner] = nonce + 1 + + log Approval(_owner, _spender, _value) + return True + + +@view +@external +def DOMAIN_SEPARATOR() -> bytes32: + """ + @notice EIP712 domain separator. + @return bytes32 Domain Separator set for the current chain. + """ + return self._domain_separator() + + +# ------------------------- AMM View Functions ------------------------------- + + +@view +@external +def get_dx(i: int128, j: int128, dy: uint256) -> uint256: + """ + @notice Calculate the current input dx given output dy + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dy Amount of `j` being received after exchange + @return Amount of `i` predicted + """ + return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) + + +@view +@external +def get_dy(i: int128, j: int128, dx: uint256) -> uint256: + """ + @notice Calculate the current output dy given input dx + @dev Index values can be found via the `coins` public getter method + @param i Index value for the coin to send + @param j Index valie of the coin to recieve + @param dx Amount of `i` being exchanged + @return Amount of `j` predicted + """ + return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) + + +@view +@external +def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing a single coin + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @return Amount of coin received + """ + return self._calc_withdraw_one_coin(_burn_amount, i)[0] + + +@view +@external +@nonreentrant('lock') +def totalSupply() -> uint256: + """ + @notice The total supply of pool LP tokens + @return self.total_supply, 18 decimals. + """ + return self.total_supply + + +@view +@external +@nonreentrant('lock') +def get_virtual_price() -> uint256: + """ + @notice The current virtual price of the pool LP token + @dev Useful for calculating profits. + The method may be vulnerable to donation-style attacks if implementation + contains rebasing tokens. For integrators, caution is advised. + @return LP token virtual price normalized to 1e18 + """ + amp: uint256 = self._A() + xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + self._stored_rates(), self._balances() + ) + D: uint256 = self.get_D(xp, amp) + # D is in the units similar to DAI (e.g. converted to precision 1e18) + # When balanced, D = n * x_u - total virtual value of the portfolio + return D * PRECISION / self.total_supply + + +@view +@external +def calc_token_amount( + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool +) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @param _amounts Amount of each coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self) + + +@view +@external +def A() -> uint256: + return self._A() / A_PRECISION + + +@view +@external +def A_precise() -> uint256: + return self._A() + + +@view +@external +def balances(i: uint256) -> uint256: + """ + @notice Get the current balance of a coin within the + pool, less the accrued admin fees + @param i Index value for the coin to query balance of + @return Token balance + """ + return self._balances()[i] + + +@view +@external +def get_balances() -> DynArray[uint256, MAX_COINS]: + return self._balances() + + +@view +@external +def stored_rates() -> DynArray[uint256, MAX_COINS]: + return self._stored_rates() + + +@view +@external +def dynamic_fee(i: int128, j: int128) -> uint256: + """ + @notice Return the fee for swapping between `i` and `j` + @param i Index value for the coin to send + @param j Index value of the coin to recieve + @return Swap fee expressed as an integer with 1e10 precision + """ + return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) + + +# --------------------------- AMM Admin Functions ---------------------------- + + +@external +def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): + + assert msg.sender == factory.admin() + + # set new fee: + assert _new_fee <= MAX_FEE + self.fee = _new_fee + + # set new offpeg_fee_multiplier: + assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum + self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier + + log ApplyNewFee(_new_fee, _new_offpeg_fee_multiplier) + + +@external +def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): + """ + @notice Set the moving average window of the price oracles. + @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + """ + assert msg.sender == factory.admin() # dev: only owner + assert 0 not in [_ma_exp_time, _D_ma_time] + + self.ma_exp_time = _ma_exp_time + self.D_ma_time = _D_ma_time + + +@external +def set_A_oracle(_oracle: address, _method_id: bytes4): + """ + @notice Set the address and method id of the external contract that + governs Amplification Factor + @dev Only settable by the Pool Manager. Pool manager can change A oracle whenever. + """ + assert msg.sender == pool_manager # dev: only pool manager + + self.A_oracle = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) + + log SetNewAOracle(_oracle) From 2ff41dedf6e692b5af9a28f27eed8b83222c98d7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:32:51 +0100 Subject: [PATCH 229/337] add gauge manager from tx.origin --- contracts/main/LiquidityGauge.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index a76496b7..6afa0965 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -173,7 +173,7 @@ def __init__(_lp_token: address): """ self.lp_token = _lp_token self.factory = msg.sender - self.manager = msg.sender + self.manager = tx.origin symbol: String[32] = ERC20Extended(_lp_token).symbol() name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") From ce98e4439690cd297229d0b63718f05f0b24ad7e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:37:39 +0100 Subject: [PATCH 230/337] set new gauge implementation --- scripts/deploy_infra.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index f63427ff..00d3441c 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -18,7 +18,7 @@ "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", "meta_amm": "0xa12A87c73718a34CD8601b5022B2C6C359142585", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", - "gauge": "0x64891ab20392a029c0f231656ff13c5ee64b730c", + "gauge": "", }, "ethereum:mainnet": { "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", @@ -26,7 +26,7 @@ "plain_amm": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", "meta_amm": "0x19bd1AB34d6ABB584b9C1D5519093bfAA7f6c7d2", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "gauge": "0xF5617D4f7514bE35fce829a1C19AE7f6c9106979", + "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", }, # Layer 2 "arbitrum:mainnet": { @@ -193,6 +193,7 @@ def deploy_infra(network, url, account, fork=False): if fork: boa.env.fork(url) logger.log("Forkmode ...") + boa.env.eoa = deploy_utils.FIDDYDEPLOYER else: logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) @@ -260,7 +261,7 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - "ethereum:mainnet", + "ethereum:sepolia", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False, From ee495c2b6a50829c52fece2d3191a7a9a1eedb58 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:09:36 +0100 Subject: [PATCH 231/337] set A_oracle only once --- contracts/main/CurveStableSwapNGAOracle.vy | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy index fe3208ba..253b93d7 100644 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -145,7 +145,7 @@ MAX_COINS_128: constant(int128) = 8 # ---------------------------- Pool Variables -------------------------------- -pool_manager: public(immutable(address)) +POOL_MANAGER: public(immutable(address)) N_COINS: public(immutable(uint256)) N_COINS_128: immutable(int128) @@ -239,12 +239,8 @@ def __init__( @notice Initialize the pool contract @param _name Name of the new plain pool. @param _symbol Symbol for the new plain pool. - @param _A Amplification co-efficient - a lower value here means - less tolerance for imbalance within the pool's assets. - Suggested values include: - * Uncollateralized algorithmic stablecoins: 5-10 - * Non-redeemable, collateralized assets: 100 - * Redeemable assets: 200-400 + @param _A Amplification co-efficient. For this implementation, the constructor + argument is inconsequential since A is inferred from an external call. @param _fee Trade fee, given as an integer with 1e10 precision. The the maximum is 1% (100000000). 50% of the fee is distributed to veCRV holders. @@ -261,7 +257,7 @@ def __init__( @param _oracles Array of rate oracle addresses. """ - pool_manager = tx.origin + POOL_MANAGER = tx.origin coins = _coins asset_types = _asset_types @@ -273,9 +269,6 @@ def __init__( factory = Factory(msg.sender) - A: uint256 = _A * A_PRECISION - self.initial_A = A - self.future_A = A self.fee = _fee self.offpeg_fee_multiplier = _offpeg_fee_multiplier @@ -1177,7 +1170,8 @@ def _A() -> uint256: uint256 ) - assert fetched_A < MAX_A # dev: fetched A is too high + assert fetched_A < A_PRECISION * MAX_A # dev: fetched A is too high + assert return fetched_A @@ -1837,7 +1831,8 @@ def set_A_oracle(_oracle: address, _method_id: bytes4): governs Amplification Factor @dev Only settable by the Pool Manager. Pool manager can change A oracle whenever. """ - assert msg.sender == pool_manager # dev: only pool manager + assert POOL_MANAGER == tx.origin # dev: only pool manager + assert self.A_oracle == 0 # dev: A_oracle already set self.A_oracle = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) From 19ff82a6092cffc2b62fae2c9d02252251832fb0 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:12:12 +0100 Subject: [PATCH 232/337] set lower bouns for fetched A --- contracts/main/CurveStableSwapNGAOracle.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy index 253b93d7..be0b0df0 100644 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -1171,7 +1171,7 @@ def _A() -> uint256: ) assert fetched_A < A_PRECISION * MAX_A # dev: fetched A is too high - assert + assert fetched_A > A_PRECISION # dev: fetched A is too low return fetched_A From 5d643a65f30e7033cd3b03e7e3ee008d73cdc620 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:13:08 +0100 Subject: [PATCH 233/337] fix comment --- contracts/main/CurveStableSwapNGAOracle.vy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy index be0b0df0..f409201b 100644 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -1170,8 +1170,8 @@ def _A() -> uint256: uint256 ) - assert fetched_A < A_PRECISION * MAX_A # dev: fetched A is too high - assert fetched_A > A_PRECISION # dev: fetched A is too low + assert fetched_A <= A_PRECISION * MAX_A # dev: fetched A is too high + assert fetched_A >= A_PRECISION # dev: fetched A is too low return fetched_A @@ -1829,7 +1829,7 @@ def set_A_oracle(_oracle: address, _method_id: bytes4): """ @notice Set the address and method id of the external contract that governs Amplification Factor - @dev Only settable by the Pool Manager. Pool manager can change A oracle whenever. + @dev Only settable by the Pool Manager once. """ assert POOL_MANAGER == tx.origin # dev: only pool manager assert self.A_oracle == 0 # dev: A_oracle already set From bdab80a0b01621e62cdac9bc7b85bb6fcfaec507 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:34:10 +0100 Subject: [PATCH 234/337] use min max to cap instead of asserts --- contracts/main/CurveStableSwapNGAOracle.vy | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy index f409201b..0bef6d7a 100644 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -1170,11 +1170,8 @@ def _A() -> uint256: uint256 ) - assert fetched_A <= A_PRECISION * MAX_A # dev: fetched A is too high - assert fetched_A >= A_PRECISION # dev: fetched A is too low - - return fetched_A - + # Cap fetched_A between: [A_PRECISION, A_PRECISION * MAX_A]: + return min(max(A_PRECISION, fetched_A), A_PRECISION * MAX_A) @pure From eeb37733502fe35d1ec3ab64cef97f95669bae0a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 10 Nov 2023 19:00:06 +0100 Subject: [PATCH 235/337] add fraxsDAI deployment script --- scripts/deploy_pool.py | 100 +++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 29 deletions(-) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 342e249c..92870c27 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -5,6 +5,7 @@ import boa from boa.network import NetworkEnv +from deployment_utils import FIDDYDEPLOYER from eth_account import Account from eth_typing import Address from rich.console import Console as RichConsole @@ -93,37 +94,22 @@ class PoolSettings: pool_settings = { - "gnosis:mainnet": { + "ethereum:mainnet": { "plain": [ - "WXDAI/USDC/USDT", # name - "3pool-ng", # symbol + "FRAXsDAI", # name + "FRAXSDAI", # symbol [ - "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", # wxdai - "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", # usdc - "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", # usdt + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0x83F20F44975D03b1b09e64809B757c47f942BEeA", # sdai ], - 1000, # A - 1000000, # fee - 20000000000, # offpeg_fee_multiplier - 865, # ma_exp_time - 0, # implementation index - [0, 0, 0], # asset_types - [b"", b"", b""], # method_ids - [ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS], # oracles - ], - "meta": [ - "0x7f90122bf0700f9e7e1f688fe926940e8839f353", # base_pool - "EURE/3CRV", # name - "eure3crvng", # symbol - "0xcb444e90d8198415266c6a2724b7900fb12fc56e", # eure - 500, # A + 1500, # A 1000000, # fee - 20000000000, # offpeg_fee_multiplier + 10000000000, # offpeg_fee_multiplier 865, # ma_exp_time 0, # implementation index - 0, # asset_types - b"", # method_ids - ZERO_ADDRESS, # oracles + [0, 3], # asset_types + [b"", b""], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS], # oracles ], } } @@ -136,14 +122,65 @@ def deploy_pool(network, url, account, pool_type, fork): if fork: boa.env.fork(url) logger.log("Forkmode ...") - boa.env.eoa = "" # set eoa address here + boa.env.eoa = FIDDYDEPLOYER # set eoa address here + else: + logger.log("Prodmode ...") + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy") + factory = factory.at(deployments[network]["factory"]) + + logger.log("Deploying pool ...") + args = pool_settings[network][pool_type] + if pool_type == "plain": + amm_address = factory.deploy_plain_pool(*args) + elif pool_type == "meta": + amm_address = factory.deploy_metapool(*args) + + logger.log(f"Deployed Plain pool {amm_address}.") + + return amm_address + + +def deploy_gauge(network, url, account, pool_addr, fork): + + logger.log(f"Deploying gauge for pool {pool_addr} on {network} ...") + + if fork: + boa.env.fork(url) + logger.log("Forkmode ...") + boa.env.eoa = FIDDYDEPLOYER # set eoa address here + assert boa.env.eoa # EOA NOT SET! + else: + logger.log("Prodmode ...") + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + + factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy") + factory = factory.at(deployments[network]["factory"]) + + logger.log("Deploying gauge ...") + gauge_address = factory.deploy_gauge(pool_addr) + + logger.log(f"Deployed Gauge {gauge_address} for pool {pool_addr}.") + + +def deploy_pool_and_gauge(network, url, account, pool_type, fork): + + logger.log(f"Deploying pool on {network} ...") + + if fork: + boa.env.fork(url) + logger.log("Forkmode ...") + boa.env.eoa = FIDDYDEPLOYER # set eoa address here else: logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) factory = boa.load_partial("./contracts/main/CurveStableSwapFactoryNG.vy") - factory = factory.at(deployments["gnosis:mainnet"]["factory"]) + factory = factory.at(deployments[network]["factory"]) logger.log("Deploying pool ...") args = pool_settings[network][pool_type] @@ -154,10 +191,15 @@ def deploy_pool(network, url, account, pool_type, fork): logger.log(f"Deployed Plain pool {amm_address}.") + gauge_address = factory.deploy_gauge(amm_address) + + logger.log(f"Deployed Gauge {gauge_address} for pool {amm_address}.") + def main(): - fork = True - deploy_pool("gnosis:mainnet", "https://gnosis.drpc.org", "FIDDYDEPLOYER", "meta", fork) + + fork = False + deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) if __name__ == "__main__": From aeca06beb0dfefec0492891fd6ab10f37918afb9 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 11 Nov 2023 00:53:57 +0100 Subject: [PATCH 236/337] add default return value for approve --- contracts/main/CurveStableSwapMetaNG.vy | 6 +++++- poetry.lock | 6 +++--- pyproject.toml | 2 +- scripts/deploy_infra.py | 5 +++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 80ac4b1e..508542a7 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -334,7 +334,11 @@ def __init__( if i < BASE_N_COINS: # Approval needed for add_liquidity operation on base pool in # _exchange_underlying: - ERC20(_base_coins[i]).approve(BASE_POOL, max_value(uint256)) + ERC20(_base_coins[i]).approve( + BASE_POOL, + max_value(uint256), + default_return_value = True + ) # For ERC4626 tokens: if asset_types[0] == 3: diff --git a/poetry.lock b/poetry.lock index 79c9e2cb..0a56f54e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "1924f4888e2ef261efb2367fce2cf467de16f529" -resolved_reference = "1924f4888e2ef261efb2367fce2cf467de16f529" +reference = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5" +resolved_reference = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4bfbf4bf7da0af835da1d07294f58a9bf4b5de283abb2957e2a2ae24d625726a" +content-hash = "4c5b91e040b8a6e4e32780c15ee09a8f358b5d1dda054625320c40f163f72a61" diff --git a/pyproject.toml b/pyproject.toml index 8c34534c..08495a71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "1924f4888e2ef261efb2367fce2cf467de16f529"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index f63427ff..33e80969 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -24,7 +24,7 @@ "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", "plain_amm": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", - "meta_amm": "0x19bd1AB34d6ABB584b9C1D5519093bfAA7f6c7d2", + "meta_amm": "0x64AFA95e0C3D8410240a4262df9Fd82B12b64eDd", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0xF5617D4f7514bE35fce829a1C19AE7f6c9106979", }, @@ -193,6 +193,7 @@ def deploy_infra(network, url, account, fork=False): if fork: boa.env.fork(url) logger.log("Forkmode ...") + boa.env.eoa = deploy_utils.FIDDYDEPLOYER # set eoa address here else: logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) @@ -261,7 +262,7 @@ def main(): deploy_infra( "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], + "http://localhost:9090", "FIDDYDEPLOYER", fork=False, ) From 7fb7a1b6858f18cbd656e896edc667da4a3a6a40 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 11 Nov 2023 01:10:13 +0100 Subject: [PATCH 237/337] fix metapool impl for mainnet only --- scripts/deploy_infra.py | 2 +- scripts/deploy_pool.py | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 33e80969..87502e73 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -26,7 +26,7 @@ "plain_amm": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", "meta_amm": "0x64AFA95e0C3D8410240a4262df9Fd82B12b64eDd", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "gauge": "0xF5617D4f7514bE35fce829a1C19AE7f6c9106979", + "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", }, # Layer 2 "arbitrum:mainnet": { diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 92870c27..84a02610 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -95,21 +95,19 @@ class PoolSettings: pool_settings = { "ethereum:mainnet": { - "plain": [ - "FRAXsDAI", # name - "FRAXSDAI", # symbol - [ - "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax - "0x83F20F44975D03b1b09e64809B757c47f942BEeA", # sdai - ], - 1500, # A + "meta": [ + "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", # 3pool + "USDV-3crv", # name + "USDV3crv", # symbol + "0x0E573Ce2736Dd9637A0b21058352e1667925C7a8", + 500, # A 1000000, # fee - 10000000000, # offpeg_fee_multiplier - 865, # ma_exp_time + 50000000000, # offpeg_fee_multiplier + 866, # ma_exp_time 0, # implementation index - [0, 3], # asset_types - [b"", b""], # method_ids - [ZERO_ADDRESS, ZERO_ADDRESS], # oracles + 0, # asset_types + b"", # method_ids + ZERO_ADDRESS, # oracles ], } } @@ -199,7 +197,7 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): fork = False - deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) + deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090", "FIDDYDEPLOYER", "meta", fork) if __name__ == "__main__": From 304d17b4ddb7e009f29e951dce88788de828494c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 11 Nov 2023 11:10:10 +0100 Subject: [PATCH 238/337] deploy usdc3crv --- contracts/main/LiquidityGauge.vy | 2 +- scripts/deploy_pool.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index a76496b7..6afa0965 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -173,7 +173,7 @@ def __init__(_lp_token: address): """ self.lp_token = _lp_token self.factory = msg.sender - self.manager = msg.sender + self.manager = tx.origin symbol: String[32] = ERC20Extended(_lp_token).symbol() name: String[64] = concat("Curve.fi ", symbol, " Gauge Deposit") diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 84a02610..d7abf1e2 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -136,7 +136,7 @@ def deploy_pool(network, url, account, pool_type, fork): elif pool_type == "meta": amm_address = factory.deploy_metapool(*args) - logger.log(f"Deployed Plain pool {amm_address}.") + logger.log(f"Deployed pool {amm_address}.") return amm_address @@ -187,7 +187,7 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): elif pool_type == "meta": amm_address = factory.deploy_metapool(*args) - logger.log(f"Deployed Plain pool {amm_address}.") + logger.log(f"Deployed pool {amm_address}.") gauge_address = factory.deploy_gauge(amm_address) @@ -197,7 +197,7 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): fork = False - deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090", "FIDDYDEPLOYER", "meta", fork) + deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "meta", fork) if __name__ == "__main__": From e6816844dcc905872fe5217ffb0226ae2af675e7 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:45:06 +0100 Subject: [PATCH 239/337] add cap to spot prices going into oracle --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 6 +++--- contracts/main/CurveStableSwapNGAOracle.vy | 3 ++- scripts/deploy_pool.py | 20 ++++++++++++++++++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 508542a7..ffb0df06 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1374,7 +1374,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # Upate packed prices ----------------- last_prices_packed_new[0] = self.pack_2( - spot_price[0], + min(spot_price[0], 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( last_prices_packed_current[0], self.ma_exp_time, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 889dca04..836f76ea 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1341,11 +1341,11 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # Upate packed prices ----------------- last_prices_packed_new[i] = self.pack_2( - spot_price[i], + min(spot_price[i], 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( last_prices_packed_current[i], self.ma_exp_time, - ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices + ma_last_time_unpacked[0], # index 0 is ma_last_time for prices ) ) @@ -1359,7 +1359,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): self._calc_moving_average( last_D_packed_current, self.D_ma_time, - ma_last_time_unpacked[1], # index 1 is ma_exp_time for D + ma_last_time_unpacked[1], # index 1 is ma_last_time for D ) ) diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy index 0bef6d7a..dcad00cf 100644 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ b/contracts/main/CurveStableSwapNGAOracle.vy @@ -178,6 +178,7 @@ admin_balances: public(DynArray[uint256, MAX_COINS]) rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] oracles: DynArray[uint256, MAX_COINS] +A_oracle: public(uint256) # For ERC4626 tokens, we need: call_amount: immutable(DynArray[uint256, MAX_COINS]) @@ -1331,7 +1332,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): # Upate packed prices ----------------- last_prices_packed_new[i] = self.pack_2( - spot_price[i], + min(spot_price[i], 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( last_prices_packed_current[i], self.ma_exp_time, diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index d7abf1e2..d66308dc 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -109,6 +109,22 @@ class PoolSettings: b"", # method_ids ZERO_ADDRESS, # oracles ], + "plain": [ + "FRAXsDAI", # name + "FRAXSDAI", # symbol + [ + "0x853d955aCEf822Db058eb8505911ED77F175b99e", # frax + "0x83F20F44975D03b1b09e64809B757c47f942BEeA", # sdai + ], + 1500, # A + 1000000, # fee + 10000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + 0, # implementation index + [0, 3], # asset_types + [b"", b""], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS], # oracles + ], } } @@ -196,8 +212,8 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): - fork = False - deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "meta", fork) + fork = True + deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) if __name__ == "__main__": From 9228406a8ca0cee11fbe0510b269354767599e07 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 15 Nov 2023 00:55:33 +0100 Subject: [PATCH 240/337] deploy new ng implementation --- scripts/deploy_infra.py | 58 ++++++++++++++++++++--------------------- tests/test_oracles.py | 37 ++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 87502e73..257c9178 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -23,7 +23,7 @@ "ethereum:mainnet": { "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", - "plain_amm": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", + "plain_amm": "0x933f4769DCC27fC7345D9d5975AE48EC4D0F829C", "meta_amm": "0x64AFA95e0C3D8410240a4262df9Fd82B12b64eDd", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", @@ -32,36 +32,36 @@ "arbitrum:mainnet": { "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", - "plain_amm": "0x76303e4fDcA0AbF28aB3ee42Ce086E6503431F1D", - "meta_amm": "0xd125E7a0cEddF89c6473412d85835450897be6Dc", + "plain_amm": "", + "meta_amm": "", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", + "plain_amm": "", + "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, "linea:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "zksync:mainnet": { @@ -74,65 +74,65 @@ "pzkevm:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87fe17697d0f14a222e8bef386a0860ecffdd617", - "plain_amm": "0x1764ee18e8b3cca4787249ceb249356192594585", - "meta_amm": "0x5eee3091f747e60a045a2e715a4c71e600e31f6e", + "plain_amm": "", + "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", + "plain_amm": "", + "meta_amm": "", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "polygon:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "avax:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "ftm:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "meta_amm": "0x686bdb3D24Bc6F3ED89ed3d3B659765c54aC78B4", + "plain_amm": "", + "meta_amm": "", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "bsc:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", - "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", + "plain_amm": "", + "meta_amm": "", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "celo:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "meta_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "tron:mainnet": { diff --git a/tests/test_oracles.py b/tests/test_oracles.py index 89e26849..8a4dc726 100644 --- a/tests/test_oracles.py +++ b/tests/test_oracles.py @@ -110,6 +110,43 @@ def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, assert approx(swap.price_oracle(n), p1, 1e-5) +@given( + amount=strategy("uint256", min_value=10**9, max_value=10**15), +) +@settings(**SETTINGS) +@pytest.mark.only_for_pool_type(0) +def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount): + + for token in pool_tokens: + if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: + return + + p_oracle_before = swap.price_oracle(0) + print("before", p_oracle_before) + + # calc amount in: + amount_in = amount * 10 ** (decimals[0]) + + # mint tokens for bob if he needs: + if amount_in > pool_tokens[0].balanceOf(bob): + mint_for_testing(bob, amount_in, pool_tokens[0], False) + + # do large swap + try: + swap.exchange(0, 1, amount_in, 0, sender=bob) + except boa.BoaError: + return # we're okay with failure to manipulate here + + # time travel + boa.env.time_travel(blocks=500) + + # check if price oracle is way too high + p_oracle_after = swap.price_oracle(0) + print("after", p_oracle_after) + + assert p_oracle_after < 2 * 10**18 + + @given( amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), From 06c712eb5b8e0aff6dba56b7e27931664b672c8c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 15 Nov 2023 07:07:21 +0100 Subject: [PATCH 241/337] redeploy metapool implementation --- scripts/deploy_infra.py | 8 ++++---- scripts/deploy_pool.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 1df35ea7..38f0a4aa 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -15,8 +15,8 @@ "ethereum:sepolia": { "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", - "plain_amm": "0x296d2b5c23833a70d07c8fcbb97d846c1ff90ddd", - "meta_amm": "0xa12A87c73718a34CD8601b5022B2C6C359142585", + "plain_amm": "", + "meta_amm": "", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", "gauge": "", }, @@ -24,7 +24,7 @@ "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", "plain_amm": "0x933f4769DCC27fC7345D9d5975AE48EC4D0F829C", - "meta_amm": "0x64AFA95e0C3D8410240a4262df9Fd82B12b64eDd", + "meta_amm": "0x1f7C86AffE5bCF7a1D74a8c8E2ef9E03BF31c1BD", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", }, @@ -262,7 +262,7 @@ def main(): deploy_infra( "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], + "http://localhost:9090/", "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index d66308dc..457cba64 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -212,8 +212,9 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): - fork = True + fork = False deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) + deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "meta", fork) if __name__ == "__main__": From 0d147196ed0eb3c3c30b82d31deeebd8606ce30c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:38:34 +0100 Subject: [PATCH 242/337] fix: metapool impl should use static arrays for zap compatibility --- contracts/main/CurveStableSwapMetaNG.vy | 36 ++- contracts/mocks/Zap.vy | 314 ++++++++++++++++++++++++ poetry.lock | 6 +- pyproject.toml | 2 +- tests/pools/meta/test_meta_zap.py | 179 ++++++++++++++ 5 files changed, 523 insertions(+), 14 deletions(-) create mode 100644 contracts/mocks/Zap.vy create mode 100644 tests/pools/meta/test_meta_zap.py diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index ffb0df06..ad740a1e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,6 +1,6 @@ # pragma version 0.3.10 # pragma optimize codesize -# pragma evm-version paris +# pragma evm-version shanghai """ @title CurveStableSwapMetaNG @author Curve.Fi @@ -741,7 +741,7 @@ def exchange_underlying( @external @nonreentrant('lock') def add_liquidity( - _amounts: DynArray[uint256, MAX_COINS], + _amounts: uint256[N_COINS], _min_mint_amount: uint256, _receiver: address = msg.sender ) -> uint256: @@ -857,7 +857,13 @@ def add_liquidity( self.total_supply = total_supply log Transfer(empty(address), _receiver, mint_amount) - log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) + log AddLiquidity( + msg.sender, + [_amounts[0], _amounts[1]], + fees, + D1, + total_supply + ) return mint_amount @@ -907,7 +913,7 @@ def remove_liquidity_one_coin( @external @nonreentrant('lock') def remove_liquidity_imbalance( - _amounts: DynArray[uint256, MAX_COINS], + _amounts: uint256[N_COINS], _max_burn_amount: uint256, _receiver: address = msg.sender ) -> uint256: @@ -976,7 +982,13 @@ def remove_liquidity_imbalance( self._burnFrom(msg.sender, burn_amount) - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + log RemoveLiquidityImbalance( + msg.sender, + [_amounts[0], _amounts[1]], + fees, + D1, + total_supply + ) return burn_amount @@ -985,10 +997,10 @@ def remove_liquidity_imbalance( @nonreentrant('lock') def remove_liquidity( _burn_amount: uint256, - _min_amounts: DynArray[uint256, MAX_COINS], + _min_amounts: uint256[N_COINS], _receiver: address = msg.sender, _claim_admin_fees: bool = True, -) -> DynArray[uint256, MAX_COINS]: +) -> uint256[N_COINS]: """ @notice Withdraw coins from the pool @dev Withdrawal amounts are based on current deposit ratios @@ -1047,7 +1059,7 @@ def remove_liquidity( if _claim_admin_fees: self._withdraw_admin_fees() - return amounts + return [amounts[0], amounts[1]] @external @@ -1729,7 +1741,7 @@ def get_virtual_price() -> uint256: @view @external def calc_token_amount( - _amounts: DynArray[uint256, MAX_COINS], + _amounts: uint256[N_COINS], _is_deposit: bool ) -> uint256: """ @@ -1738,7 +1750,11 @@ def calc_token_amount( @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self) + return StableSwapViews(factory.views_implementation()).calc_token_amount( + [_amounts[0], _amounts[1]], + _is_deposit, + self + ) @view diff --git a/contracts/mocks/Zap.vy b/contracts/mocks/Zap.vy new file mode 100644 index 00000000..b245916e --- /dev/null +++ b/contracts/mocks/Zap.vy @@ -0,0 +1,314 @@ +# @version 0.3.10 +""" +@title "Zap" Depositer for permissionless USD metapools +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2021 - all rights reserved +""" + +interface ERC20: + def transfer(_receiver: address, _amount: uint256): nonpayable + def transferFrom(_sender: address, _receiver: address, _amount: uint256): nonpayable + def approve(_spender: address, _amount: uint256): nonpayable + def decimals() -> uint256: view + def balanceOf(_owner: address) -> uint256: view + +interface CurveMeta: + def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256, _receiver: address) -> uint256: nonpayable + def remove_liquidity(_amount: uint256, min_amounts: uint256[N_COINS]) -> uint256[N_COINS]: nonpayable + def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256, _receiver: address) -> uint256: nonpayable + def remove_liquidity_imbalance(amounts: uint256[N_COINS], max_burn_amount: uint256) -> uint256: nonpayable + def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view + def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256: view + def coins(i: uint256) -> address: view + +interface CurveBase: + def add_liquidity(amounts: uint256[BASE_N_COINS], min_mint_amount: uint256): nonpayable + def remove_liquidity(_amount: uint256, min_amounts: uint256[BASE_N_COINS]): nonpayable + def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable + def remove_liquidity_imbalance(amounts: uint256[BASE_N_COINS], max_burn_amount: uint256): nonpayable + def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view + def calc_token_amount(amounts: uint256[BASE_N_COINS], deposit: bool) -> uint256: view + def coins(i: uint256) -> address: view + def fee() -> uint256: view + + +N_COINS: constant(uint256) = 2 +MAX_COIN: constant(uint256) = N_COINS-1 +BASE_N_COINS: constant(uint256) = 3 +N_ALL_COINS: constant(uint256) = N_COINS + BASE_N_COINS - 1 + +N_COINS_128: constant(int128) = 2 +MAX_COIN_128: constant(int128) = N_COINS-1 +BASE_N_COINS_128: constant(int128) = 3 +N_ALL_COINS_128: constant(int128) = N_COINS + BASE_N_COINS - 1 + +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee + +BASE_POOL: immutable(address) +BASE_LP_TOKEN: immutable(address) +BASE_COINS: immutable(address[3]) + +# coin -> pool -> is approved to transfer? +is_approved: HashMap[address, HashMap[address, bool]] + + +@external +def __init__(_base_pool: address, _base_lp_token: address, _base_coins: address[3]): + """ + @notice Contract constructor + """ + + BASE_POOL = _base_pool + BASE_LP_TOKEN = _base_lp_token + BASE_COINS = _base_coins + + base_coins: address[3] = BASE_COINS + for coin in base_coins: + ERC20(coin).approve(BASE_POOL, MAX_UINT256) + + +@external +def add_liquidity( + _pool: address, + _deposit_amounts: uint256[N_ALL_COINS], + _min_mint_amount: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Wrap underlying coins and deposit them into `_pool` + @param _pool Address of the pool to deposit into + @param _deposit_amounts List of amounts of underlying coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that receives the LP tokens + @return Amount of LP tokens received by depositing + """ + meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) + deposit_base: bool = False + base_coins: address[3] = BASE_COINS + + if _deposit_amounts[0] != 0: + coin: address = CurveMeta(_pool).coins(0) + if not self.is_approved[coin][_pool]: + ERC20(coin).approve(_pool, MAX_UINT256) + self.is_approved[coin][_pool] = True + ERC20(coin).transferFrom(msg.sender, self, _deposit_amounts[0]) + meta_amounts[0] = _deposit_amounts[0] + + for i in range(1, N_ALL_COINS): + amount: uint256 = _deposit_amounts[i] + if amount == 0: + continue + deposit_base = True + base_idx: uint256 = i - 1 + coin: address = base_coins[base_idx] + + ERC20(coin).transferFrom(msg.sender, self, amount) + # Handle potential Tether fees + if i == N_ALL_COINS - 1: + base_amounts[base_idx] = ERC20(coin).balanceOf(self) + else: + base_amounts[base_idx] = amount + + # Deposit to the base pool + if deposit_base: + coin: address = BASE_LP_TOKEN + CurveBase(BASE_POOL).add_liquidity(base_amounts, 0) + meta_amounts[MAX_COIN] = ERC20(coin).balanceOf(self) + if not self.is_approved[coin][_pool]: + ERC20(coin).approve(_pool, MAX_UINT256) + self.is_approved[coin][_pool] = True + + # Deposit to the meta pool + return CurveMeta(_pool).add_liquidity(meta_amounts, _min_mint_amount, _receiver) + + +@external +def remove_liquidity( + _pool: address, + _burn_amount: uint256, + _min_amounts: uint256[N_ALL_COINS], + _receiver: address = msg.sender +) -> uint256[N_ALL_COINS]: + """ + @notice Withdraw and unwrap coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _pool Address of the pool to deposit into + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the LP tokens + @return List of amounts of underlying coins that were withdrawn + """ + ERC20(_pool).transferFrom(msg.sender, self, _burn_amount) + + min_amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) + amounts: uint256[N_ALL_COINS] = empty(uint256[N_ALL_COINS]) + + # Withdraw from meta + meta_received: uint256[N_COINS] = CurveMeta(_pool).remove_liquidity( + _burn_amount, + [_min_amounts[0], convert(0, uint256)] + ) + + # Withdraw from base + for i in range(BASE_N_COINS): + min_amounts_base[i] = _min_amounts[MAX_COIN+i] + CurveBase(BASE_POOL).remove_liquidity(meta_received[1], min_amounts_base) + + # Transfer all coins out + coin: address = CurveMeta(_pool).coins(0) + ERC20(coin).transfer(_receiver, meta_received[0]) + amounts[0] = meta_received[0] + + base_coins: address[BASE_N_COINS] = BASE_COINS + for i in range(1, N_ALL_COINS): + coin = base_coins[i-1] + amounts[i] = ERC20(coin).balanceOf(self) + ERC20(coin).transfer(_receiver, amounts[i]) + + return amounts + + +@external +def remove_liquidity_one_coin( + _pool: address, + _burn_amount: uint256, + i: int128, + _min_amount: uint256, + _receiver: address=msg.sender +) -> uint256: + """ + @notice Withdraw and unwrap a single coin from the pool + @param _pool Address of the pool to deposit into + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_amount Minimum amount of underlying coin to receive + @param _receiver Address that receives the LP tokens + @return Amount of underlying coin received + """ + ERC20(_pool).transferFrom(msg.sender, self, _burn_amount) + + coin_amount: uint256 = 0 + if i == 0: + coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, i, _min_amount, _receiver) + else: + base_coins: address[BASE_N_COINS] = BASE_COINS + coin: address = base_coins[i - MAX_COIN_128] + # Withdraw a base pool coin + coin_amount = CurveMeta(_pool).remove_liquidity_one_coin(_burn_amount, MAX_COIN_128, 0, self) + CurveBase(BASE_POOL).remove_liquidity_one_coin(coin_amount, i-MAX_COIN_128, _min_amount) + coin_amount = ERC20(coin).balanceOf(self) + ERC20(coin).transfer(_receiver, coin_amount) + + return coin_amount + + +@external +def remove_liquidity_imbalance( + _pool: address, + _amounts: uint256[N_ALL_COINS], + _max_burn_amount: uint256, + _receiver: address=msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _pool Address of the pool to deposit into + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the LP tokens + @return Actual amount of the LP token burned in the withdrawal + """ + fee: uint256 = CurveBase(BASE_POOL).fee() * BASE_N_COINS / (4 * (BASE_N_COINS - 1)) + fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision + + # Transfer the LP token in + ERC20(_pool).transferFrom(msg.sender, self, _max_burn_amount) + + withdraw_base: bool = False + amounts_base: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) + amounts_meta: uint256[N_COINS] = empty(uint256[N_COINS]) + + # determine amounts to withdraw from base pool + for i in range(BASE_N_COINS): + amount: uint256 = _amounts[MAX_COIN + i] + if amount != 0: + amounts_base[i] = amount + withdraw_base = True + + # determine amounts to withdraw from metapool + amounts_meta[0] = _amounts[0] + if withdraw_base: + amounts_meta[MAX_COIN] = CurveBase(BASE_POOL).calc_token_amount(amounts_base, False) + amounts_meta[MAX_COIN] += amounts_meta[MAX_COIN] * fee / FEE_DENOMINATOR + 1 + + # withdraw from metapool and return the remaining LP tokens + burn_amount: uint256 = CurveMeta(_pool).remove_liquidity_imbalance(amounts_meta, _max_burn_amount) + ERC20(_pool).transfer(msg.sender, _max_burn_amount - burn_amount) + + # withdraw from base pool + if withdraw_base: + CurveBase(BASE_POOL).remove_liquidity_imbalance(amounts_base, amounts_meta[MAX_COIN]) + coin: address = BASE_LP_TOKEN + leftover: uint256 = ERC20(coin).balanceOf(self) + + if leftover > 0: + # if some base pool LP tokens remain, re-deposit them for the caller + if not self.is_approved[coin][_pool]: + ERC20(coin).approve(_pool, MAX_UINT256) + self.is_approved[coin][_pool] = True + burn_amount -= CurveMeta(_pool).add_liquidity([convert(0, uint256), leftover], 0, msg.sender) + + # transfer withdrawn base pool tokens to caller + base_coins: address[BASE_N_COINS] = BASE_COINS + for i in range(BASE_N_COINS): + ERC20(base_coins[i]).transfer(_receiver, amounts_base[i]) + + # transfer withdrawn metapool tokens to caller + if _amounts[0] > 0: + coin: address = CurveMeta(_pool).coins(0) + ERC20(coin).transfer(_receiver, _amounts[0]) + + return burn_amount + + +@view +@external +def calc_withdraw_one_coin(_pool: address, _token_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing and unwrapping a single coin + @param _pool Address of the pool to deposit into + @param _token_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the underlying coin to withdraw + @return Amount of coin received + """ + if i < MAX_COIN_128: + return CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, i) + else: + _base_tokens: uint256 = CurveMeta(_pool).calc_withdraw_one_coin(_token_amount, MAX_COIN_128) + return CurveBase(BASE_POOL).calc_withdraw_one_coin(_base_tokens, i-MAX_COIN_128) + + +@view +@external +def calc_token_amount(_pool: address, _amounts: uint256[N_ALL_COINS], _is_deposit: bool) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @dev This calculation accounts for slippage, but not fees. + Needed to prevent front-running, not for precise calculations! + @param _pool Address of the pool to deposit into + @param _amounts Amount of each underlying coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + meta_amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + base_amounts: uint256[BASE_N_COINS] = empty(uint256[BASE_N_COINS]) + + meta_amounts[0] = _amounts[0] + for i in range(BASE_N_COINS): + base_amounts[i] = _amounts[i + MAX_COIN] + + base_tokens: uint256 = CurveBase(BASE_POOL).calc_token_amount(base_amounts, _is_deposit) + meta_amounts[MAX_COIN] = base_tokens + + return CurveMeta(_pool).calc_token_amount(meta_amounts, _is_deposit) diff --git a/poetry.lock b/poetry.lock index 0a56f54e..4b82f300 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5" -resolved_reference = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5" +reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" +resolved_reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4c5b91e040b8a6e4e32780c15ee09a8f358b5d1dda054625320c40f163f72a61" +content-hash = "40670953f2e928ef535bfedc35d1c0ea4fafd22fff5e96d6123780ba2b92b3cb" diff --git a/pyproject.toml b/pyproject.toml index 08495a71..24b897d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "b5e9fb96d1424ed5cc5a6af03391d885439c83e5"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7171aee25c4d25fc1626a361a8c972e9316fd383"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/pools/meta/test_meta_zap.py b/tests/pools/meta/test_meta_zap.py new file mode 100644 index 00000000..7e803fe9 --- /dev/null +++ b/tests/pools/meta/test_meta_zap.py @@ -0,0 +1,179 @@ +import warnings + +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + +warnings.filterwarnings("ignore") + + +@pytest.fixture(scope="module") +def meta_token(deployer): + with boa.env.prank(deployer): + return boa.load( + "contracts/mocks/ERC20.vy", + "OTA", + "OTA", + 18, + ) + + +@pytest.fixture(scope="module") +def metapool_tokens(meta_token, base_pool): + return [meta_token, base_pool] + + +@pytest.fixture(scope="module") +def tokens_all(meta_token, base_pool_tokens): + return [meta_token] + base_pool_tokens + + +@pytest.fixture(scope="module") +def add_base_pool( + owner, + factory, + base_pool, + base_pool_lp_token, + base_pool_tokens, +): + with boa.env.prank(owner): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + [0] * len(base_pool_tokens), + len(base_pool_tokens), + ) + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + base_pool, + amm_interface_meta, + add_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + asset_type = meta_token.asset_type() + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return amm_interface_meta.at(pool) + + +@pytest.fixture(scope="module") +def zap(base_pool, base_pool_tokens, base_pool_lp_token): + return boa.load( + "contracts/mocks/Zap.vy", base_pool.address, base_pool_lp_token.address, [a.address for a in base_pool_tokens] + ) + + +@pytest.fixture(scope="module") +def swap(zap, base_pool, empty_swap, charlie, tokens_all): + + for i in range(3): + assert base_pool.balances(i) == 0 + + deposit_amount = 100 * 10**18 + + for token in tokens_all: + mint_for_testing(charlie, deposit_amount, token, False) + token.approve(zap.address, 2**256 - 1, sender=charlie) + + deposit_amounts = [deposit_amount] * 4 + + out_amount = zap.add_liquidity(empty_swap.address, deposit_amounts, 0, sender=charlie) + assert out_amount > 0 + assert 0 not in empty_swap.get_balances() + assert empty_swap.totalSupply() > 0 + + return empty_swap + + +def test_calc_amts_add(zap, swap, charlie, tokens_all): + + deposit_amount = 2 * 100 * 10**18 + + for token in tokens_all: + mint_for_testing(charlie, deposit_amount, token, False) + token.approve(zap.address, 2**256 - 1, sender=charlie) + + deposit_amounts = [deposit_amount] * 4 + + calc_amt_zap = zap.calc_token_amount(swap.address, deposit_amounts, True) + out_amount = zap.add_liquidity(swap.address, deposit_amounts, 0, sender=charlie) + + assert calc_amt_zap == out_amount + + +def test_calc_amts_remove_imbalance(zap, swap, charlie, tokens_all): + + to_receive_amounts = [10 * 10**18] * 4 + + charlie_bal_before = [] + for token in tokens_all: + charlie_bal_before.append(token.balanceOf(charlie)) + + swap.approve(zap, 2**256 - 1, sender=charlie) + calc_burnt_amt_zap = zap.calc_token_amount(swap.address, to_receive_amounts, False) + actual_burnt_amt = zap.remove_liquidity_imbalance( + swap.address, [int(0.9 * amt) for amt in to_receive_amounts], calc_burnt_amt_zap, sender=charlie + ) + + assert actual_burnt_amt <= calc_burnt_amt_zap + + for i, token in enumerate(tokens_all): + assert token.balanceOf(charlie) > charlie_bal_before[i] + + +def test_calc_amts_remove(zap, swap, charlie, tokens_all, meta_token, base_pool, base_pool_tokens): + + charlie_bal_before = [] + for _t in tokens_all: + charlie_bal_before.append(_t.balanceOf(charlie)) + + charlie_lp_bal_before = swap.balanceOf(charlie) + + with boa.env.anchor(): + amts_received = swap.remove_liquidity(charlie_lp_bal_before, [0, 0], sender=charlie) + base_amts_received = base_pool.remove_liquidity(amts_received[1], [0, 0, 0], sender=charlie) + total_expected_received = [amts_received[0]] + base_amts_received + + total_token_balances = [meta_token.balanceOf(swap)] + [_t.balanceOf(base_pool) for _t in base_pool_tokens] + + swap.approve(zap, 2**256 - 1, sender=charlie) + total_received_amount = zap.remove_liquidity(swap.address, charlie_lp_bal_before, [0] * 4, sender=charlie) + + # tokens owned by zap: + zap_balances = [] + for token in tokens_all: + zap_balances.append(token.balanceOf(zap)) + + charlie_bal_after = [] + for _t in tokens_all: + charlie_bal_after.append(_t.balanceOf(charlie)) + + for i in range(len(tokens_all)): + assert total_token_balances[i] == total_received_amount[i] == total_expected_received[i] From f720b1a92569257efd62a6fce285aec0d4b2dbc6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:54:05 +0100 Subject: [PATCH 243/337] precise tests --- contracts/main/CurveStableSwapMetaNG.vy | 16 +++++---- tests/pools/meta/test_meta_zap.py | 45 +++++++++++++------------ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index ad740a1e..805c1e9b 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -752,6 +752,7 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ + amounts: DynArray[uint256, MAX_COINS] = [_amounts[0], _amounts[1]] amp: uint256 = self._A() old_balances: DynArray[uint256, MAX_COINS] = self._balances() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() @@ -766,12 +767,12 @@ def add_liquidity( for i in range(N_COINS_128): - if _amounts[i] > 0: + if amounts[i] > 0: new_balances[i] += self._transfer_in( i, -1, # <--- we're not handling underlying coins here - _amounts[i], + amounts[i], msg.sender, False, # expect_optimistic_transfer ) @@ -859,7 +860,7 @@ def add_liquidity( log AddLiquidity( msg.sender, - [_amounts[0], _amounts[1]], + amounts, fees, D1, total_supply @@ -924,6 +925,7 @@ def remove_liquidity_imbalance( @param _receiver Address that receives the withdrawn coins @return Actual amount of the LP token burned in the withdrawal """ + amounts: DynArray[uint256, MAX_COINS] = [_amounts[0], _amounts[1]] amp: uint256 = self._A() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() old_balances: DynArray[uint256, MAX_COINS] = self._balances() @@ -932,9 +934,9 @@ def remove_liquidity_imbalance( for i in range(N_COINS_128): - if _amounts[i] != 0: - new_balances[i] -= _amounts[i] - self._transfer_out(i, _amounts[i], _receiver) + if amounts[i] != 0: + new_balances[i] -= amounts[i] + self._transfer_out(i, amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) @@ -984,7 +986,7 @@ def remove_liquidity_imbalance( log RemoveLiquidityImbalance( msg.sender, - [_amounts[0], _amounts[1]], + amounts, fees, D1, total_supply diff --git a/tests/pools/meta/test_meta_zap.py b/tests/pools/meta/test_meta_zap.py index 7e803fe9..2735b10d 100644 --- a/tests/pools/meta/test_meta_zap.py +++ b/tests/pools/meta/test_meta_zap.py @@ -91,20 +91,21 @@ def zap(base_pool, base_pool_tokens, base_pool_lp_token): @pytest.fixture(scope="module") -def swap(zap, base_pool, empty_swap, charlie, tokens_all): +def initial_amts(): + return [100 * 10**18] * 4 + + +@pytest.fixture(scope="module") +def swap(zap, base_pool, empty_swap, charlie, tokens_all, initial_amts): for i in range(3): assert base_pool.balances(i) == 0 - deposit_amount = 100 * 10**18 - for token in tokens_all: - mint_for_testing(charlie, deposit_amount, token, False) + mint_for_testing(charlie, initial_amts[0], token, False) token.approve(zap.address, 2**256 - 1, sender=charlie) - deposit_amounts = [deposit_amount] * 4 - - out_amount = zap.add_liquidity(empty_swap.address, deposit_amounts, 0, sender=charlie) + out_amount = zap.add_liquidity(empty_swap.address, initial_amts, 0, sender=charlie) assert out_amount > 0 assert 0 not in empty_swap.get_balances() assert empty_swap.totalSupply() > 0 @@ -128,24 +129,26 @@ def test_calc_amts_add(zap, swap, charlie, tokens_all): assert calc_amt_zap == out_amount -def test_calc_amts_remove_imbalance(zap, swap, charlie, tokens_all): - - to_receive_amounts = [10 * 10**18] * 4 - - charlie_bal_before = [] - for token in tokens_all: - charlie_bal_before.append(token.balanceOf(charlie)) +def test_calc_amts_remove_imbalance( + zap, swap, meta_token, base_pool_tokens, base_pool_lp_token, base_pool, charlie, tokens_all, initial_amts +): + amounts = [i // 4 for i in initial_amts] + initial_balance = swap.balanceOf(charlie) swap.approve(zap, 2**256 - 1, sender=charlie) - calc_burnt_amt_zap = zap.calc_token_amount(swap.address, to_receive_amounts, False) - actual_burnt_amt = zap.remove_liquidity_imbalance( - swap.address, [int(0.9 * amt) for amt in to_receive_amounts], calc_burnt_amt_zap, sender=charlie - ) - - assert actual_burnt_amt <= calc_burnt_amt_zap + max_burn = swap.balanceOf(charlie) + zap.remove_liquidity_imbalance(swap, amounts, max_burn, sender=charlie) + # check if charlie received what was wanted: for i, token in enumerate(tokens_all): - assert token.balanceOf(charlie) > charlie_bal_before[i] + assert token.balanceOf(charlie) == amounts[i] + + # bob is the only LP, total supply is affected in the same way as his balance + assert swap.balanceOf(charlie) < initial_balance + assert swap.balanceOf(charlie) >= initial_balance - max_burn + + assert swap.balanceOf(zap) == 0 + assert swap.balanceOf(charlie) == swap.totalSupply() def test_calc_amts_remove(zap, swap, charlie, tokens_all, meta_token, base_pool, base_pool_tokens): From 02162cbd1537356f43dcc0e8c334c3a2cc503e96 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 16 Nov 2023 22:07:39 +0100 Subject: [PATCH 244/337] new metapool impl and pools --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- scripts/deploy_infra.py | 2 +- scripts/deploy_pool.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 805c1e9b..44169357 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,6 +1,6 @@ # pragma version 0.3.10 # pragma optimize codesize -# pragma evm-version shanghai +# pragma evm-version paris """ @title CurveStableSwapMetaNG @author Curve.Fi diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 38f0a4aa..bfd393cc 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -24,7 +24,7 @@ "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", "plain_amm": "0x933f4769DCC27fC7345D9d5975AE48EC4D0F829C", - "meta_amm": "0x1f7C86AffE5bCF7a1D74a8c8E2ef9E03BF31c1BD", + "meta_amm": "0xDD7EBB1C49780519dD9755B8B1A23a6f42CE099E", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", }, diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 457cba64..885d6946 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -214,7 +214,7 @@ def main(): fork = False deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) - deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "meta", fork) + deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090/", "FIDDYDEPLOYER", "meta", fork) if __name__ == "__main__": From 5f582a6b8f709d863825c5fbe026cd3b4fa2d840 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:13:03 +0100 Subject: [PATCH 245/337] deployments --- poetry.lock | 6 ++-- pyproject.toml | 2 +- scripts/deploy_infra.py | 62 ++++++++++++++++++++--------------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4b82f300..5f62ffc5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4136,8 +4136,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" -resolved_reference = "7171aee25c4d25fc1626a361a8c972e9316fd383" +reference = "b4e66665612ea0b27c1a286dd598bb02a6a973ff" +resolved_reference = "b4e66665612ea0b27c1a286dd598bb02a6a973ff" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "40670953f2e928ef535bfedc35d1c0ea4fafd22fff5e96d6123780ba2b92b3cb" +content-hash = "790076c1b81fd47dcd25d479baeaeda746bd494c430712367a17105c3205d4a6" diff --git a/pyproject.toml b/pyproject.toml index 24b897d9..6fb65c00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "7171aee25c4d25fc1626a361a8c972e9316fd383"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "b4e66665612ea0b27c1a286dd598bb02a6a973ff"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index bfd393cc..ab06045e 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -32,36 +32,36 @@ "arbitrum:mainnet": { "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x0458ea5f4cd00e873264be2031ceb8f9d9b3116c", + "meta_amm": "0xc6c09471ee39c7e30a067952fcc89c8922f9ab53", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", + "meta_amm": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, "linea:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "meta_amm": "0x604388bb1159afd21eb5191ce22b4decdee2ae22", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "meta_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "zksync:mainnet": { @@ -74,65 +74,65 @@ "pzkevm:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87fe17697d0f14a222e8bef386a0860ecffdd617", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xc9Fe0C63Af9A39402e8a5514f9c43Af0322b665F", + "meta_amm": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "polygon:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", + "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "avax:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", + "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "ftm:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "meta_amm": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "bsc:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x64379C265Fc6595065D7d835AAaa731c0584dB80", + "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "celo:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", + "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", + "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "meta_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "tron:mainnet": { @@ -261,9 +261,9 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - "ethereum:mainnet", - "http://localhost:9090/", - "FIDDYDEPLOYER", + ":mainnet", + os.environ["RPC_"], + "", fork=False, ) From cf74a8869218087fec7f3194c0b9673930094a93 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:01:17 +0100 Subject: [PATCH 246/337] fix: asset_type specific _transfer_out logic --- contracts/main/CurveStableSwapMetaNG.vy | 37 ++++++++++++++----------- contracts/main/CurveStableSwapNG.vy | 19 +++++++------ poetry.lock | 8 +++--- pyproject.toml | 2 +- tests/pools/test_virtual_price.py | 29 +++++++++++++++++++ 5 files changed, 66 insertions(+), 29 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 44169357..f48683de 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -480,18 +480,20 @@ def _transfer_out( @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ + if asset_types[0] != 2: - coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) - - # ------------------------- Handle Transfers ----------------------------- - - assert ERC20(coins[_coin_idx]).transfer( - receiver, _amount, default_return_value=True - ) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + self.stored_balances[_coin_idx] -= _amount - # ----------------------- Update Stored Balances ------------------------- + else: - self.stored_balances[_coin_idx] = coin_balance - _amount + coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + self.stored_balances[_coin_idx] = coin_balance - _amount # -------------------------- AMM Special Methods ----------------------------- @@ -1086,9 +1088,12 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: return _fee xps2: uint256 = (xpi + xpj) ** 2 - return ( - (_offpeg_fee_multiplier * _fee) / - (unsafe_sub(_offpeg_fee_multiplier, FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + return unsafe_div( + unsafe_mul(_offpeg_fee_multiplier, _fee), + unsafe_add( + unsafe_sub(_offpeg_fee_multiplier, FEE_DENOMINATOR) * 4 * xpi * xpj / xps2, + FEE_DENOMINATOR + ) ) @@ -1321,7 +1326,7 @@ def _calc_withdraw_one_coin( dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + dy = unsafe_div((dy - 1) * PRECISION, rates[i]) # Withdraw less to account for rounding errors # calculate state price xp[i] = new_y @@ -1431,10 +1436,10 @@ def _calc_moving_average( if ma_last_time < block.timestamp: # calculate new_ema_value and return that. alpha: uint256 = math.exp( -convert( - (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 + unsafe_div(unsafe_mul(unsafe_sub(block.timestamp, ma_last_time), 10**18), averaging_window), int256 ) ) - return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 + return unsafe_div(last_spot_value * (10**18 - alpha) + last_ema_value * alpha, 10**18) return last_ema_value @@ -1870,7 +1875,7 @@ def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ assert msg.sender == factory.admin() # dev: only owner - assert 0 not in [_ma_exp_time, _D_ma_time] + assert unsafe_mul(_ma_exp_time, _D_ma_time) > 0 # dev: 0 in input values self.ma_exp_time = _ma_exp_time self.D_ma_time = _D_ma_time diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 836f76ea..a2f21b56 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -399,17 +399,20 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): @param receiver Address to send the tokens to """ - coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + if not 2 in asset_types: - # ------------------------- Handle Transfers ----------------------------- - - assert ERC20(coins[_coin_idx]).transfer( - receiver, _amount, default_return_value=True - ) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + self.stored_balances[_coin_idx] -= _amount - # ----------------------- Update Stored Balances ------------------------- + else: - self.stored_balances[_coin_idx] = coin_balance - _amount + coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) + assert ERC20(coins[_coin_idx]).transfer( + receiver, _amount, default_return_value=True + ) + self.stored_balances[_coin_idx] = coin_balance - _amount # -------------------------- AMM Special Methods ----------------------------- diff --git a/poetry.lock b/poetry.lock index 5f62ffc5..ac747502 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4135,9 +4135,9 @@ forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/vyperlang/titanoboa.git" -reference = "b4e66665612ea0b27c1a286dd598bb02a6a973ff" -resolved_reference = "b4e66665612ea0b27c1a286dd598bb02a6a973ff" +url = "https://github.com/vyperlang/titanoboa" +reference = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47" +resolved_reference = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47" [[package]] name = "tomli" @@ -4805,4 +4805,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "790076c1b81fd47dcd25d479baeaeda746bd494c430712367a17105c3205d4a6" +content-hash = "1e7274a173b8da3e549cdcf97685b564373a0517b39e5465f442c9e2a02e1aee" diff --git a/pyproject.toml b/pyproject.toml index 6fb65c00..a4d73cbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "b4e66665612ea0b27c1a286dd598bb02a6a973ff"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa", rev = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py index a239a2ca..4674ac0a 100644 --- a/tests/pools/test_virtual_price.py +++ b/tests/pools/test_virtual_price.py @@ -1,5 +1,7 @@ import pytest +from tests.utils.tokens import mint_for_testing + pytestmark = pytest.mark.usefixtures("initial_setup") @@ -60,3 +62,30 @@ def test_exchange(bob, swap, sending, receiving, decimals): ) assert swap.get_virtual_price() > virtual_price + + +def test_pump_virtual_price(bob, swap, pool_tokens, initial_amounts, pool_size): + + # make a deposit + for i, amount in enumerate(initial_amounts): + amounts = [0] * pool_size + amounts[i] = amount + swap.add_liquidity(amounts, 0, sender=bob) + + virtual_price_pre_donation = swap.get_virtual_price() + + # airdrop some tokens to increase the price. + pool_balances_post_donation = [] + for i, token in enumerate(pool_tokens): + to_deposit = token.balanceOf(swap) + mint_for_testing(swap.address, to_deposit, token, False) # directly mint to pool + pool_balances_post_donation.append(token.balanceOf(swap)) + + virtual_price_post_donation = swap.get_virtual_price() + assert virtual_price_pre_donation == virtual_price_post_donation + + # remove liquidity in one coin. + swap.remove_liquidity_one_coin(swap.balanceOf(bob), 0, 0, sender=bob) + virtual_price_post_withdrawal = swap.get_virtual_price() + + assert virtual_price_post_withdrawal < 1.05 * virtual_price_post_donation From 4bb402ecb386979c113bee770ffbea9aebd5ae66 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:24:50 +0100 Subject: [PATCH 247/337] fix: use correct input for oracle update in remove_liquidity_imbalance --- contracts/main/CurveStableSwapMetaNG.vy | 211 ++- contracts/main/CurveStableSwapNG.vy | 3 +- poetry.lock | 1620 +---------------------- pyproject.toml | 1 - scripts/deploy_infra.py | 60 +- tests/pools/test_virtual_price.py | 2 +- 6 files changed, 130 insertions(+), 1767 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index f48683de..68200bf2 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -201,8 +201,8 @@ BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) -asset_types: immutable(DynArray[uint8, MAX_COINS]) -stored_balances: DynArray[uint256, MAX_COINS] +asset_types: immutable(uint8[N_COINS]) +stored_balances: uint256[N_COINS] # Fee specific vars FEE_DENOMINATOR: constant(uint256) = 10 ** 10 @@ -229,15 +229,15 @@ admin_balances: public(DynArray[uint256, MAX_COINS]) # ----------------------- Oracle Specific vars ------------------------------- -rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) +rate_multipliers: immutable(uint256[N_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: DynArray[uint256, MAX_COINS] +oracles: uint256[N_COINS] # For ERC4626 tokens, we need: call_amount: immutable(uint256) scale_factor: immutable(uint256) -last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price +last_prices_packed: uint256 # packing: last_price, ma_price last_D_packed: uint256 # packing: last_D, ma_D ma_exp_time: public(uint256) D_ma_time: public(uint256) @@ -327,8 +327,8 @@ def __init__( BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) coins = _coins # <---------------- coins[1] is always base pool LP token. - asset_types = _asset_types - rate_multipliers = _rate_multipliers + asset_types = [_asset_types[0], _asset_types[1]] + rate_multipliers = [_rate_multipliers[0], _rate_multipliers[1]] for i in range(MAX_COINS): if i < BASE_N_COINS: @@ -365,10 +365,10 @@ def __init__( # ------------------- initialize storage for DynArrays ------------------ - self.last_prices_packed.append(self.pack_2(10**18, 10**18)) + self.last_prices_packed = self.pack_2(10**18, 10**18) for i in range(N_COINS_128): - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) self.admin_balances = [0, 0] self.stored_balances = [0, 0] @@ -501,18 +501,15 @@ def _transfer_out( @view @internal -def _stored_rates() -> DynArray[uint256, MAX_COINS]: +def _stored_rates() -> uint256[N_COINS]: """ @notice Gets rate multipliers for each coin. @dev If the coin has a rate oracle that has been properly initialised, this method queries that rate by static-calling an external contract. """ - rates: DynArray[uint256, MAX_COINS] = [ - rate_multipliers[0], - StableSwap(BASE_POOL).get_virtual_price() - ] - oracles: DynArray[uint256, MAX_COINS] = self.oracles + rates: uint256[N_COINS] = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] + oracles: uint256[N_COINS] = self.oracles if asset_types[0] == 1 and not self.oracles[0] == 0: @@ -543,7 +540,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: @view @internal -def _balances() -> DynArray[uint256, MAX_COINS]: +def _balances() -> uint256[N_COINS]: """ @notice Calculates the pool's balances _excluding_ the admin's balances. @dev If the pool contains rebasing tokens, this method ensures LPs keep all @@ -552,17 +549,14 @@ def _balances() -> DynArray[uint256, MAX_COINS]: the fees in the rebasing token that the admin collects is immune to slashing events. """ - result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances_i: uint256 = 0 - + result: uint256[N_COINS] = empty(uint256[N_COINS]) + admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128): if 2 in asset_types: - balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] + result[i] = ERC20(coins[i]).balanceOf(self) - admin_balances[i] else: - balances_i = self.stored_balances[i] - self.admin_balances[i] - - result.append(balances_i) + result[i] = self.stored_balances[i] - admin_balances[i] return result @@ -652,9 +646,9 @@ def exchange_underlying( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) dy: uint256 = 0 base_i: int128 = 0 @@ -754,27 +748,26 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ - amounts: DynArray[uint256, MAX_COINS] = [_amounts[0], _amounts[1]] amp: uint256 = self._A() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() # Initial invariant D0: uint256 = self.get_D_mem(rates, old_balances, amp) total_supply: uint256 = self.total_supply - new_balances: DynArray[uint256, MAX_COINS] = old_balances + new_balances: uint256[N_COINS] = old_balances # -------------------------- Do Transfers In ----------------------------- for i in range(N_COINS_128): - if amounts[i] > 0: + if _amounts[i] > 0: new_balances[i] += self._transfer_in( i, -1, # <--- we're not handling underlying coins here - amounts[i], + _amounts[i], msg.sender, False, # expect_optimistic_transfer ) @@ -791,7 +784,7 @@ def add_liquidity( # We need to recalculate the invariant accounting for fees # to calculate fair user's share - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + fees: uint256[N_COINS] = empty(uint256[N_COINS]) mint_amount: uint256 = 0 if total_supply > 0: @@ -823,19 +816,14 @@ def add_liquidity( # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) - fees.append( - unsafe_div( - _dynamic_fee_i * difference, - FEE_DENOMINATOR - ) - ) + fees[i] = unsafe_div(_dynamic_fee_i * difference, FEE_DENOMINATOR) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) new_balances[i] -= fees[i] - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D1 = math.get_D(xp, amp, N_COINS) # <------ Reuse D1 for new D value. + xp: uint256[N_COINS] = self._xp_mem(rates, new_balances) + D1 = math.get_D([xp[0], xp[1]], amp, N_COINS) # <------ Reuse D1 for new D value. # we do unsafe div here because we already did several safedivs with D0 mint_amount = unsafe_div(total_supply * (D1 - D0), D0) self.upkeep_oracles(xp, amp, D1) @@ -862,8 +850,8 @@ def add_liquidity( log AddLiquidity( msg.sender, - amounts, - fees, + [_amounts[0], _amounts[1]], + [fees[0], fees[1]], D1, total_supply ) @@ -890,7 +878,7 @@ def remove_liquidity_one_coin( assert _burn_amount > 0 # dev: do not remove 0 LP tokens dy: uint256 = 0 fee: uint256 = 0 - xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: uint256[N_COINS] = empty(uint256[N_COINS]) amp: uint256 = empty(uint256) D: uint256 = empty(uint256) @@ -927,18 +915,18 @@ def remove_liquidity_imbalance( @param _receiver Address that receives the withdrawn coins @return Actual amount of the LP token burned in the withdrawal """ - amounts: DynArray[uint256, MAX_COINS] = [_amounts[0], _amounts[1]] + amp: uint256 = self._A() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() D0: uint256 = self.get_D_mem(rates, old_balances, amp) - new_balances: DynArray[uint256, MAX_COINS] = old_balances + new_balances: uint256[N_COINS] = old_balances for i in range(N_COINS_128): - if amounts[i] != 0: - new_balances[i] -= amounts[i] - self._transfer_out(i, amounts[i], _receiver) + if _amounts[i] != 0: + new_balances[i] -= _amounts[i] + self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) @@ -946,7 +934,7 @@ def remove_liquidity_imbalance( # ys: uint256 = (D0 + D1) / N_COINS ys: uint256 = unsafe_div(D0 + D1, N_COINS) - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + fees: uint256[N_COINS] = empty(uint256[N_COINS]) dynamic_fee: uint256 = 0 xs: uint256 = 0 ideal_balance: uint256 = 0 @@ -966,7 +954,7 @@ def remove_liquidity_imbalance( # base_fee * difference / FEE_DENOMINATOR xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees.append(unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR)) + fees[i] = unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR) # fees[i] * admin_fee / FEE_DENOMINATOR self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) @@ -974,8 +962,7 @@ def remove_liquidity_imbalance( new_balances[i] -= fees[i] D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. - - self.upkeep_oracles(new_balances, amp, D1) + self.upkeep_oracles(self._xp_mem(rates, new_balances), amp, D1) total_supply: uint256 = self.total_supply # here we can do unsafe div by D0 because we did several safedivs: @@ -988,8 +975,8 @@ def remove_liquidity_imbalance( log RemoveLiquidityImbalance( msg.sender, - amounts, - fees, + [_amounts[0], _amounts[1]], + [fees[0], fees[1]], D1, total_supply ) @@ -1015,8 +1002,8 @@ def remove_liquidity( """ total_supply: uint256 = self.total_supply assert _burn_amount > 0 # dev: invalid _burn_amount - amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances: DynArray[uint256, MAX_COINS] = self._balances() + amounts: uint256[N_COINS] = empty(uint256[N_COINS]) + balances: uint256[N_COINS] = self._balances() value: uint256 = 0 @@ -1024,7 +1011,7 @@ def remove_liquidity( value = unsafe_div(balances[i] * _burn_amount, total_supply) assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts.append(value) + amounts[i] = value self._transfer_out(i, value, _receiver) self._burnFrom(msg.sender, _burn_amount) # dev: insufficient funds @@ -1053,7 +1040,7 @@ def remove_liquidity( log RemoveLiquidity( msg.sender, - amounts, + [amounts[0], amounts[1]], empty(DynArray[uint256, MAX_COINS]), unsafe_sub(total_supply, _burn_amount) ) @@ -1100,15 +1087,15 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: @internal def __exchange( x: uint256, - _xp: DynArray[uint256, MAX_COINS], - rates: DynArray[uint256, MAX_COINS], + _xp: uint256[N_COINS], + rates: uint256[N_COINS], i: int128, j: int128, ) -> uint256: amp: uint256 = self._A() - D: uint256 = math.get_D(_xp, amp, N_COINS) - y: uint256 = math.get_y(i, j, x, _xp, amp, D, N_COINS) + D: uint256 = math.get_D([_xp[0], _xp[1]], amp, N_COINS) + y: uint256 = math.get_y(i, j, x, [_xp[0], _xp[1]], amp, D, N_COINS) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors dy_fee: uint256 = unsafe_div( @@ -1128,7 +1115,7 @@ def __exchange( ) # Calculate and store state prices: - xp: DynArray[uint256, MAX_COINS] = _xp + xp: uint256[N_COINS] = _xp xp[i] = x xp[j] = y # D is not changed because we did not apply a fee @@ -1151,9 +1138,9 @@ def _exchange( assert i != j # dev: coin index out of range assert _dx > 0 # dev: do not exchange 0 coins - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) + rates: uint256[N_COINS] = self._stored_rates() + old_balances: uint256[N_COINS] = self._balances() + xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) # --------------------------- Do Transfer in ----------------------------- @@ -1247,15 +1234,12 @@ def _A() -> uint256: @pure @internal -def _xp_mem( - _rates: DynArray[uint256, MAX_COINS], - _balances: DynArray[uint256, MAX_COINS] -) -> DynArray[uint256, MAX_COINS]: +def _xp_mem(_rates: uint256[N_COINS], _balances: uint256[N_COINS]) -> uint256[N_COINS]: - result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + result: uint256[N_COINS] = empty(uint256[N_COINS]) for i in range(N_COINS_128): # _rates[i] * _balances[i] / PRECISION - result.append(unsafe_div(_rates[i] * _balances[i], PRECISION)) + result[i] = unsafe_div(_rates[i] * _balances[i], PRECISION) return result @@ -1263,12 +1247,12 @@ def _xp_mem( @view @internal def get_D_mem( - _rates: DynArray[uint256, MAX_COINS], - _balances: DynArray[uint256, MAX_COINS], + _rates: uint256[N_COINS], + _balances: uint256[N_COINS], _amp: uint256 ) -> uint256: - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) - return math.get_D(xp, _amp, N_COINS) + xp: uint256[N_COINS] = self._xp_mem(_rates, _balances) + return math.get_D([xp[0], xp[1]], _amp, N_COINS) @view @@ -1279,7 +1263,7 @@ def _calc_withdraw_one_coin( ) -> ( uint256, uint256, - DynArray[uint256, MAX_COINS], + uint256[N_COINS], uint256, uint256 ): @@ -1290,15 +1274,15 @@ def _calc_withdraw_one_coin( # get pool state amp: uint256 = self._A() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) - D0: uint256 = math.get_D(xp, amp, N_COINS) + rates: uint256[N_COINS] = self._stored_rates() + xp: uint256[N_COINS] = self._xp_mem(rates, self._balances()) + D0: uint256 = math.get_D([xp[0], xp[1]], amp, N_COINS) total_supply: uint256 = self.total_supply D1: uint256 = D0 - _burn_amount * D0 / total_supply - new_y: uint256 = math.get_y_D(amp, i, xp, D1, N_COINS) + new_y: uint256 = math.get_y_D(amp, i, [xp[0], xp[1]], D1, N_COINS) - xp_reduced: DynArray[uint256, MAX_COINS] = xp + xp_reduced: uint256[N_COINS] = xp # ys: uint256 = (D0 + D1) / (2 * N_COINS) ys: uint256 = unsafe_div((D0 + D1), unsafe_mul(2, N_COINS)) # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) @@ -1324,7 +1308,7 @@ def _calc_withdraw_one_coin( dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) xp_reduced[j] = xp_j - unsafe_div(dynamic_fee * dx_expected, FEE_DENOMINATOR) - dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, xp_reduced, D1, N_COINS) + dy: uint256 = xp_reduced[i] - math.get_y_D(amp, i, [xp_reduced[0], xp_reduced[1]], D1, N_COINS) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees dy = unsafe_div((dy - 1) * PRECISION, rates[i]) # Withdraw less to account for rounding errors @@ -1353,10 +1337,10 @@ def unpack_2(packed: uint256) -> uint256[2]: @internal @pure def _get_p( - xp: DynArray[uint256, MAX_COINS], + xp: uint256[N_COINS], amp: uint256, D: uint256, -) -> DynArray[uint256, MAX_COINS]: +) -> uint256: # dx_0 / dx_1 only, however can have any number of coins in pool ANN: uint256 = unsafe_mul(amp, N_COINS) @@ -1365,37 +1349,32 @@ def _get_p( for i in range(N_COINS_128): Dr = Dr * D / xp[i] - p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) # ANN * xp[0] / A_PRECISION xp0_A: uint256 = unsafe_div(ANN * xp[0], A_PRECISION) - p.append( - 10**18 * (xp0_A + unsafe_div(Dr * xp[0], xp[1])) / (xp0_A + Dr) - ) - - return p + return 10**18 * (xp0_A + unsafe_div(Dr * xp[0], xp[1])) / (xp0_A + Dr) @internal -def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): +def upkeep_oracles(xp: uint256[N_COINS], amp: uint256, D: uint256): """ @notice Upkeeps price and D oracles. """ ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) - last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed - last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current + last_prices_packed_current: uint256 = self.last_prices_packed + last_prices_packed_new: uint256 = last_prices_packed_current - spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D) + spot_price: uint256 = self._get_p(xp, amp, D) # -------------------------- Upkeep price oracle ------------------------- # Metapools are always 2-coin pools, so we care about idx=0 only: - if spot_price[0] != 0: + if spot_price != 0: # Upate packed prices ----------------- - last_prices_packed_new[0] = self.pack_2( - min(spot_price[0], 2 * 10**18), # <----- Cap spot value by 2. + last_prices_packed_new = self.pack_2( + min(spot_price, 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( - last_prices_packed_current[0], + last_prices_packed_current, self.ma_exp_time, ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices ) @@ -1447,13 +1426,15 @@ def _calc_moving_average( @view @external def last_price(i: uint256) -> uint256: - return self.last_prices_packed[i] & (2**128 - 1) + assert i == 0 # dev: metapools do not have last_price indices greater than 0. + return self.last_prices_packed & (2**128 - 1) @view @external def ema_price(i: uint256) -> uint256: - return (self.last_prices_packed[i] >> 128) + assert i == 0 # dev: metapools do not have ema_price indices greater than 0. + return (self.last_prices_packed >> 128) @external @@ -1465,23 +1446,23 @@ def get_p(i: uint256) -> uint256: @param i index of state price (0 for coin[1], 1 for coin[2], ...) @return uint256 The state price quoted by the AMM for coin[i+1] """ - assert i == 0 # dev: metapools do not have price oracle indices greater than 0. + assert i == 0 # dev: metapools do not have get_p indices greater than 0. amp: uint256 = self._A() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem( + xp: uint256[N_COINS] = self._xp_mem( self._stored_rates(), self._balances() ) - D: uint256 = math.get_D(xp, amp, N_COINS) - return self._get_p(xp, amp, D)[0] + D: uint256 = math.get_D([xp[0], xp[1]], amp, N_COINS) + return self._get_p(xp, amp, D) @external @view @nonreentrant('lock') def price_oracle(i: uint256) -> uint256: - assert i == 0 # dev: metapools do not have price oracle indices greater than 0. + assert i == 0 # dev: metapools do not have price_oracle indices greater than 0. return self._calc_moving_average( - self.last_prices_packed[0], + self.last_prices_packed, self.ma_exp_time, self.ma_last_time & (2**128 - 1), ) @@ -1738,8 +1719,8 @@ def get_virtual_price() -> uint256: contains rebasing tokens. For integrators, caution is advised. @return LP token virtual price normalized to 1e18 """ - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(self._stored_rates(), self._balances()) - D: uint256 = math.get_D(xp, self._A(), N_COINS) + xp: uint256[N_COINS] = self._xp_mem(self._stored_rates(), self._balances()) + D: uint256 = math.get_D([xp[0], xp[1]], self._A(), N_COINS) # D is in the units similar to DAI (e.g. converted to precision 1e18) # When balanced, D = n * x_u - total virtual value of the portfolio return D * PRECISION / self.total_supply @@ -1791,13 +1772,15 @@ def balances(i: uint256) -> uint256: @view @external def get_balances() -> DynArray[uint256, MAX_COINS]: - return self._balances() + balances: uint256[N_COINS] = self._balances() + return [balances[0], balances[1]] @view @external def stored_rates() -> DynArray[uint256, MAX_COINS]: - return self._stored_rates() + rates: uint256[N_COINS] = self._stored_rates() + return [rates[0], rates[1]] @view diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index a2f21b56..760c6b21 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -774,8 +774,7 @@ def remove_liquidity_imbalance( new_balances[i] -= fees[i] D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. - - self.upkeep_oracles(new_balances, amp, D1) + self.upkeep_oracles(self._xp_mem(rates, new_balances), amp, D1) total_supply: uint256 = self.total_supply burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 diff --git a/poetry.lock b/poetry.lock index ac747502..a40132fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,138 +1,5 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. -[[package]] -name = "aiohttp" -version = "3.8.6" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" -attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] - [[package]] name = "appnope" version = "0.1.3" @@ -161,17 +28,6 @@ six = ">=1.12.0" [package.extras] test = ["astroid", "pytest"] -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "attrs" version = "23.1.0" @@ -201,18 +57,6 @@ files = [ {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -[[package]] -name = "base58" -version = "1.0.3" -description = "Base58 and Base58Check implementation" -optional = false -python-versions = "*" -files = [ - {file = "base58-1.0.3-py2-none-any.whl", hash = "sha256:1e42993c0628ed4f898c03b522b26af78fb05115732549b21a028bc4633d19ab"}, - {file = "base58-1.0.3-py3-none-any.whl", hash = "sha256:6aa0553e477478993588303c54659d15e3c17ae062508c854a8b752d07c716bd"}, - {file = "base58-1.0.3.tar.gz", hash = "sha256:9a793c599979c497800eb414c852b80866f28daaed5494703fc129592cc83e60"}, -] - [[package]] name = "bitarray" version = "2.8.2" @@ -917,23 +761,6 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - [[package]] name = "distlib" version = "0.3.7" @@ -1096,57 +923,6 @@ doc = ["Sphinx (>=1.6.5,<5)", "jinja2 (>=3.0.0,<3.1.0)", "sphinx-rtd-theme (>=0. lint = ["black (>=22,<23)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)"] test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.25.0)"] -[[package]] -name = "eth-ape" -version = "0.6.22" -description = "Ape Ethereum Framework" -optional = false -python-versions = ">=3.8,<4" -files = [ - {file = "eth-ape-0.6.22.tar.gz", hash = "sha256:dcadd800a532cf253111340c4bd027624fde85755993d147d0233b137a0ad99f"}, - {file = "eth_ape-0.6.22-py3-none-any.whl", hash = "sha256:a68d9ccb0acd533662625da0247854f64e8fd6cd031f83a64b1b96a54a990881"}, -] - -[package.dependencies] -click = ">=8.1.6,<9" -eip712 = ">=0.2.1,<0.3" -eth-abi = ">=4.1.0,<5" -eth-account = ">=0.8,<0.9" -eth-typing = ">=3.4,<4" -eth-utils = ">=2.2.0,<3" -ethpm-types = ">=0.5.8,<0.6" -evm-trace = ">=0.1.0a23" -hexbytes = ">=0.2.3,<1" -ijson = ">=3.1.4,<4" -importlib-metadata = "*" -ipython = ">=8.5.0,<9" -lazyasd = ">=0.1.4" -packaging = ">=23.0,<24" -pandas = ">=1.3.0,<2" -pluggy = ">=1.3,<2" -py-geth = ">=3.13.0,<4" -pydantic = ">=1.10.8,<3" -PyGithub = ">=1.59,<2" -pytest = ">=6.0,<8.0" -python-dateutil = ">=2.8.2,<3" -PyYAML = ">=5.0,<7" -requests = ">=2.28.1,<3" -rich = ">=12.5.1,<13" -SQLAlchemy = ">=1.4.35" -tqdm = ">=4.62.3,<5.0" -traitlets = ">=5.3.0" -urllib3 = ">=1.26.16,<2" -watchdog = ">=3.0,<4" -web3 = {version = ">=6.7.0,<7", extras = ["tester"]} - -[package.extras] -dev = ["Sphinx (>=6.1.3,<7)", "black (>=23.9.1,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.5.1,<2)", "myst-parser (>=1.0.0,<2)", "pandas-stubs (==1.2.0.62)", "pre-commit", "pydantic (<2.0)", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine (==3.8.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools", "wheel"] -doc = ["Sphinx (>=6.1.3,<7)", "myst-parser (>=1.0.0,<2)", "sphinx-click (>=4.4.0,<5)", "sphinx-plausible (>=0.1.2,<0.2)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0,<2)", "flake8-print (>=4.0.1,<5)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mdformat-pyproject (>=0.0.1)", "mypy (>=1.5.1,<2)", "pandas-stubs (==1.2.0.62)", "pydantic (<2.0)", "types-PyYAML", "types-SQLAlchemy (>=1.4.49)", "types-requests", "types-setuptools"] -recommended-plugins = ["ape-alchemy", "ape-ens", "ape-etherscan", "ape-foundry", "ape-hardhat", "ape-infura", "ape-solidity", "ape-template", "ape-tokens", "ape-vyper"] -release = ["setuptools", "twine (==3.8.0)", "wheel"] -test = ["hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pytest-cov (>=4.0.0,<5)", "pytest-mock", "pytest-xdist"] - [[package]] name = "eth-bloom" version = "2.0.0" @@ -1180,7 +956,6 @@ files = [ [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} -safe-pysha3 = {version = ">=1.0.0", optional = true, markers = "python_version >= \"3.9\" and extra == \"pysha3\""} [package.extras] dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] @@ -1273,38 +1048,6 @@ pycryptodome = ">=3.18.0,<4.0.0" [package.extras] hypothesis = ["hypothesis (>=6.58.0,<7.0.0)"] -[[package]] -name = "eth-tester" -version = "0.9.1b1" -description = "Tools for testing Ethereum applications." -optional = false -python-versions = ">=3.6.8,<4" -files = [ - {file = "eth-tester-0.9.1b1.tar.gz", hash = "sha256:b9cbc93d0b17a6e8acbb52294dad214ee223cf88209fa5be66ead353937d274c"}, - {file = "eth_tester-0.9.1b1-py3-none-any.whl", hash = "sha256:0e4367d99ae242efdb8c1d18ed99d1ff3f03149abb0a4c2427bc6d333ebef13b"}, -] - -[package.dependencies] -eth-abi = ">=3.0.1" -eth-account = ">=0.6.0" -eth-hash = [ - {version = ">=0.1.4,<1.0.0", extras = ["pysha3"], optional = true, markers = "implementation_name == \"cpython\" and extra == \"py-evm\""}, - {version = ">=0.1.4,<1.0.0", extras = ["pycryptodome"], optional = true, markers = "implementation_name == \"pypy\" and extra == \"py-evm\""}, -] -eth-keys = ">=0.4.0,<0.5.0" -eth-utils = ">=2.0.0,<3.0.0" -py-evm = {version = "0.7.0a4", optional = true, markers = "extra == \"py-evm\""} -rlp = ">=3.0.0,<4" -semantic-version = ">=2.6.0,<3.0.0" - -[package.extras] -dev = ["black (>=22,<23)", "bumpversion (>=0.5.3,<1.0.0)", "eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "flake8 (>=3.5.0,<4.0.0)", "py-evm (==0.7.0a4)", "pytest (>=7.0.0)", "pytest-xdist (>=2.0.0,<3)", "towncrier (>=21,<22)", "tox (>=2.9.1,<3.0.0)", "wheel (>=0.30.0,<1.0.0)"] -docs = ["towncrier (>=21,<22)"] -lint = ["black (>=22,<23)", "flake8 (>=3.5.0,<4.0.0)"] -py-evm = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "py-evm (==0.7.0a4)"] -pyevm = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "eth-hash[pysha3] (>=0.1.4,<1.0.0)", "py-evm (==0.7.0a4)"] -test = ["eth-hash[pycryptodome] (>=0.1.4,<1.0.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.0.0,<3)"] - [[package]] name = "eth-typing" version = "3.5.0" @@ -1348,55 +1091,6 @@ docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)" lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] -[[package]] -name = "ethpm-types" -version = "0.5.8" -description = "ethpm_types: Implementation of EIP-2678" -optional = false -python-versions = ">=3.8,<4" -files = [ - {file = "ethpm-types-0.5.8.tar.gz", hash = "sha256:deaa9ec75cc8d02a047d5a2b065bd038ee6654ba2ffcf6e4c969181eacfdffc2"}, - {file = "ethpm_types-0.5.8-py3-none-any.whl", hash = "sha256:1fcf4fd551133ec917b99406b206713f34112f5e3c030ece5dcb3e5ed8562ee3"}, -] - -[package.dependencies] -eth-utils = ">=2.1.0,<3" -hexbytes = ">=0.3.0,<1" -py-cid = ">=0.3.0,<0.4" -pydantic = ">=1.10.7,<2.0.dev0 || >=2.3.dev0,<3" -requests = ">=2.29.0,<3" - -[package.extras] -dev = ["IPython", "PyGithub (>=1.54,<2.0)", "black (>=23.9.1,<24)", "commitizen (>=2.40,<2.41)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "ipdb", "isort (>=5.10.1,<5.11)", "mypy (>=1.5.1,<2)", "pre-commit", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-requests", "types-setuptools", "wheel"] -doc = ["Sphinx (>=4.4.0,<5.0)", "myst-parser (>=0.17.0,<0.18)", "sphinx-click (>=3.1.0,<4.0)", "sphinx-rtd-theme (>=1.0.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "flake8-breakpoint (>=1.1.0)", "flake8-print (>=4.0.0)", "isort (>=5.10.1,<5.11)", "mypy (>=1.5.1,<2)", "types-requests", "types-setuptools"] -release = ["setuptools", "twine", "wheel"] -test = ["PyGithub (>=1.54,<2.0)", "hypothesis (>=6.2.0,<7.0)", "hypothesis-jsonschema (==0.19.0)", "pysha3 (>=1.0.2,<2.0.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "evm-trace" -version = "0.1.0a25" -description = "evm-trace: Ethereum Virtual Machine transaction tracing tool" -optional = false -python-versions = ">=3.8,<4" -files = [ - {file = "evm-trace-0.1.0a25.tar.gz", hash = "sha256:0e5b6d6977bf42c3a5157ee3c5cdc5e57bd23827855283b516fa4e68d09e32e2"}, - {file = "evm_trace-0.1.0a25-py3-none-any.whl", hash = "sha256:5cd30ba28dcb2c7ba2461c124ad9059629c78bd0781f5c3f2a9939427f50cb47"}, -] - -[package.dependencies] -eth-utils = ">=2.1,<3" -ethpm-types = ">=0.5.0,<0.6" -msgspec = ">=0.8" -py-evm = ">=0.7.0a3,<0.8" -pydantic = ">=1.10.1,<3" - -[package.extras] -dev = ["IPython", "black (>=23.9.1,<24)", "commitizen", "eth-hash[pysha3]", "flake8 (>=6.1.0,<7)", "hypothesis (>=6.2.0,<7.0)", "ipdb", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.5.1,<2)", "pre-commit", "pytest (>=6.0)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "twine", "types-setuptools", "wheel"] -lint = ["black (>=23.9.1,<24)", "flake8 (>=6.1.0,<7)", "isort (>=5.10.1,<6)", "mdformat (>=0.7.17)", "mdformat-frontmatter (>=0.4.1)", "mdformat-gfm (>=0.3.5)", "mypy (>=1.5.1,<2)", "types-setuptools"] -release = ["setuptools", "twine", "wheel"] -test = ["eth-hash[pysha3]", "hypothesis (>=6.2.0,<7.0)", "pytest (>=6.0)", "pytest-cov", "pytest-xdist"] - [[package]] name = "exceptiongroup" version = "1.1.3" @@ -1486,151 +1180,6 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" -[[package]] -name = "frozenlist" -version = "1.4.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, -] - -[[package]] -name = "greenlet" -version = "3.0.0" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, - {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, - {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, - {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, - {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, - {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, - {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, - {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, - {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, - {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, - {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, - {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, - {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, - {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, - {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, - {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, - {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, - {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, -] - -[package.extras] -docs = ["Sphinx"] -test = ["objgraph", "psutil"] - [[package]] name = "hexbytes" version = "0.3.1" @@ -1726,93 +1275,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "ijson" -version = "3.2.3" -description = "Iterative JSON parser with standard Python iterator interfaces" -optional = false -python-versions = "*" -files = [ - {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a4ae076bf97b0430e4e16c9cb635a6b773904aec45ed8dcbc9b17211b8569ba"}, - {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cfced0a6ec85916eb8c8e22415b7267ae118eaff2a860c42d2cc1261711d0d31"}, - {file = "ijson-3.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b9d1141cfd1e6d6643aa0b4876730d0d28371815ce846d2e4e84a2d4f471cf3"}, - {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0a27db6454edd6013d40a956d008361aac5bff375a9c04ab11fc8c214250b5"}, - {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0d526ccb335c3c13063c273637d8611f32970603dfb182177b232d01f14c23"}, - {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:545a30b3659df2a3481593d30d60491d1594bc8005f99600e1bba647bb44cbb5"}, - {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9680e37a10fedb3eab24a4a7e749d8a73f26f1a4c901430e7aa81b5da15f7307"}, - {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2a80c0bb1053055d1599e44dc1396f713e8b3407000e6390add72d49633ff3bb"}, - {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f05ed49f434ce396ddcf99e9fd98245328e99f991283850c309f5e3182211a79"}, - {file = "ijson-3.2.3-cp310-cp310-win32.whl", hash = "sha256:b4eb2304573c9fdf448d3fa4a4fdcb727b93002b5c5c56c14a5ffbbc39f64ae4"}, - {file = "ijson-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:923131f5153c70936e8bd2dd9dcfcff43c67a3d1c789e9c96724747423c173eb"}, - {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:904f77dd3d87736ff668884fe5197a184748eb0c3e302ded61706501d0327465"}, - {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0974444c1f416e19de1e9f567a4560890095e71e81623c509feff642114c1e53"}, - {file = "ijson-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1a4b8eb69b6d7b4e94170aa991efad75ba156b05f0de2a6cd84f991def12ff9"}, - {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d052417fd7ce2221114f8d3b58f05a83c1a2b6b99cafe0b86ac9ed5e2fc889df"}, - {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b8064a85ec1b0beda7dd028e887f7112670d574db606f68006c72dd0bb0e0e2"}, - {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaac293853f1342a8d2a45ac1f723c860f700860e7743fb97f7b76356df883a8"}, - {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6c32c18a934c1dc8917455b0ce478fd7a26c50c364bd52c5a4fb0fc6bb516af7"}, - {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:713a919e0220ac44dab12b5fed74f9130f3480e55e90f9d80f58de129ea24f83"}, - {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a3a6a2fbbe7550ffe52d151cf76065e6b89cfb3e9d0463e49a7e322a25d0426"}, - {file = "ijson-3.2.3-cp311-cp311-win32.whl", hash = "sha256:6a4db2f7fb9acfb855c9ae1aae602e4648dd1f88804a0d5cfb78c3639bcf156c"}, - {file = "ijson-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccd6be56335cbb845f3d3021b1766299c056c70c4c9165fb2fbe2d62258bae3f"}, - {file = "ijson-3.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eeb286639649fb6bed37997a5e30eefcacddac79476d24128348ec890b2a0ccb"}, - {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:396338a655fb9af4ac59dd09c189885b51fa0eefc84d35408662031023c110d1"}, - {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e0243d166d11a2a47c17c7e885debf3b19ed136be2af1f5d1c34212850236ac"}, - {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85afdb3f3a5d0011584d4fa8e6dccc5936be51c27e84cd2882fe904ca3bd04c5"}, - {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4fc35d569eff3afa76bfecf533f818ecb9390105be257f3f83c03204661ace70"}, - {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:455d7d3b7a6aacfb8ab1ebcaf697eedf5be66e044eac32508fccdc633d995f0e"}, - {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c63f3d57dbbac56cead05b12b81e8e1e259f14ce7f233a8cbe7fa0996733b628"}, - {file = "ijson-3.2.3-cp36-cp36m-win32.whl", hash = "sha256:a4d7fe3629de3ecb088bff6dfe25f77be3e8261ed53d5e244717e266f8544305"}, - {file = "ijson-3.2.3-cp36-cp36m-win_amd64.whl", hash = "sha256:96190d59f015b5a2af388a98446e411f58ecc6a93934e036daa75f75d02386a0"}, - {file = "ijson-3.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35194e0b8a2bda12b4096e2e792efa5d4801a0abb950c48ade351d479cd22ba5"}, - {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1053fb5f0b010ee76ca515e6af36b50d26c1728ad46be12f1f147a835341083"}, - {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:211124cff9d9d139dd0dfced356f1472860352c055d2481459038b8205d7d742"}, - {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92dc4d48e9f6a271292d6079e9fcdce33c83d1acf11e6e12696fb05c5889fe74"}, - {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3dcc33ee56f92a77f48776014ddb47af67c33dda361e84371153c4f1ed4434e1"}, - {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98c6799925a5d1988da4cd68879b8eeab52c6e029acc45e03abb7921a4715c4b"}, - {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4252e48c95cd8ceefc2caade310559ab61c37d82dfa045928ed05328eb5b5f65"}, - {file = "ijson-3.2.3-cp37-cp37m-win32.whl", hash = "sha256:644f4f03349ff2731fd515afd1c91b9e439e90c9f8c28292251834154edbffca"}, - {file = "ijson-3.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ba33c764afa9ecef62801ba7ac0319268a7526f50f7601370d9f8f04e77fc02b"}, - {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4b2ec8c2a3f1742cbd5f36b65e192028e541b5fd8c7fd97c1fc0ca6c427c704a"}, - {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dc357da4b4ebd8903e77dbcc3ce0555ee29ebe0747c3c7f56adda423df8ec89"}, - {file = "ijson-3.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bcc51c84bb220ac330122468fe526a7777faa6464e3b04c15b476761beea424f"}, - {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8d54b624629f9903005c58d9321a036c72f5c212701bbb93d1a520ecd15e370"}, - {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6ea7c7e3ec44742e867c72fd750c6a1e35b112f88a917615332c4476e718d40"}, - {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:916acdc5e504f8b66c3e287ada5d4b39a3275fc1f2013c4b05d1ab9933671a6c"}, - {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81815b4184b85ce124bfc4c446d5f5e5e643fc119771c5916f035220ada29974"}, - {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b49fd5fe1cd9c1c8caf6c59f82b08117dd6bea2ec45b641594e25948f48f4169"}, - {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:86b3c91fdcb8ffb30556c9669930f02b7642de58ca2987845b04f0d7fe46d9a8"}, - {file = "ijson-3.2.3-cp38-cp38-win32.whl", hash = "sha256:a729b0c8fb935481afe3cf7e0dadd0da3a69cc7f145dbab8502e2f1e01d85a7c"}, - {file = "ijson-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:d34e049992d8a46922f96483e96b32ac4c9cffd01a5c33a928e70a283710cd58"}, - {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9c2a12dcdb6fa28f333bf10b3a0f80ec70bc45280d8435be7e19696fab2bc706"}, - {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1844c5b57da21466f255a0aeddf89049e730d7f3dfc4d750f0e65c36e6a61a7c"}, - {file = "ijson-3.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ec3e5ff2515f1c40ef6a94983158e172f004cd643b9e4b5302017139b6c96e4"}, - {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46bafb1b9959872a1f946f8dd9c6f1a30a970fc05b7bfae8579da3f1f988e598"}, - {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab4db9fee0138b60e31b3c02fff8a4c28d7b152040553b6a91b60354aebd4b02"}, - {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4bc87e69d1997c6a55fff5ee2af878720801ff6ab1fb3b7f94adda050651e37"}, - {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e9fd906f0c38e9f0bfd5365e1bed98d649f506721f76bb1a9baa5d7374f26f19"}, - {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e84d27d1acb60d9102728d06b9650e5b7e5cb0631bd6e3dfadba8fb6a80d6c2f"}, - {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2cc04fc0a22bb945cd179f614845c8b5106c0b3939ee0d84ce67c7a61ac1a936"}, - {file = "ijson-3.2.3-cp39-cp39-win32.whl", hash = "sha256:e641814793a037175f7ec1b717ebb68f26d89d82cfd66f36e588f32d7e488d5f"}, - {file = "ijson-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:6bd3e7e91d031f1e8cea7ce53f704ab74e61e505e8072467e092172422728b22"}, - {file = "ijson-3.2.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06f9707da06a19b01013f8c65bf67db523662a9b4a4ff027e946e66c261f17f0"}, - {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be8495f7c13fa1f622a2c6b64e79ac63965b89caf664cc4e701c335c652d15f2"}, - {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7596b42f38c3dcf9d434dddd50f46aeb28e96f891444c2b4b1266304a19a2c09"}, - {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbac4e9609a1086bbad075beb2ceec486a3b138604e12d2059a33ce2cba93051"}, - {file = "ijson-3.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:db2d6341f9cb538253e7fe23311d59252f124f47165221d3c06a7ed667ecd595"}, - {file = "ijson-3.2.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fa8b98be298efbb2588f883f9953113d8a0023ab39abe77fe734b71b46b1220a"}, - {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:674e585361c702fad050ab4c153fd168dc30f5980ef42b64400bc84d194e662d"}, - {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd12e42b9cb9c0166559a3ffa276b4f9fc9d5b4c304e5a13668642d34b48b634"}, - {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d31e0d771d82def80cd4663a66de277c3b44ba82cd48f630526b52f74663c639"}, - {file = "ijson-3.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ce4c70c23521179d6da842bb9bc2e36bb9fad1e0187e35423ff0f282890c9ca"}, - {file = "ijson-3.2.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39f551a6fbeed4433c85269c7c8778e2aaea2501d7ebcb65b38f556030642c17"}, - {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b14d322fec0de7af16f3ef920bf282f0dd747200b69e0b9628117f381b7775b"}, - {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7851a341429b12d4527ca507097c959659baf5106c7074d15c17c387719ffbcd"}, - {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3bf1b42191b5cc9b6441552fdcb3b583594cb6b19e90d1578b7cbcf80d0fae"}, - {file = "ijson-3.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6f662dc44362a53af3084d3765bb01cd7b4734d1f484a6095cad4cb0cbfe5374"}, - {file = "ijson-3.2.3.tar.gz", hash = "sha256:10294e9bf89cb713da05bc4790bdff616610432db561964827074898e174f917"}, -] - [[package]] name = "importlib-metadata" version = "6.8.0" @@ -2037,16 +1499,6 @@ interegular = ["interegular (>=0.3.1,<0.4.0)"] nearley = ["js2py"] regex = ["regex"] -[[package]] -name = "lazyasd" -version = "0.1.4" -description = "Lazy & self-destructive tools for speeding up module imports" -optional = false -python-versions = "*" -files = [ - {file = "lazyasd-0.1.4.tar.gz", hash = "sha256:a3196f05cff27f952ad05767e5735fd564b4ea4e89b23f5ea1887229c3db145b"}, -] - [[package]] name = "lockfile" version = "0.12.2" @@ -2207,16 +1659,6 @@ files = [ {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] -[[package]] -name = "morphys" -version = "1.0" -description = "Smart conversions between unicode and bytes types for common cases" -optional = false -python-versions = "*" -files = [ - {file = "morphys-1.0-py2.py3-none-any.whl", hash = "sha256:76d6dbaa4d65f597e59d332c81da786d83e4669387b9b2a750cfec74e7beec20"}, -] - [[package]] name = "msgpack" version = "1.0.7" @@ -2282,141 +1724,6 @@ files = [ {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, ] -[[package]] -name = "msgspec" -version = "0.18.4" -description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgspec-0.18.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d24a291a3c94a7f5e26e8f5ef93e72bf26c10dfeed4d6ae8fc87ead02f4e265"}, - {file = "msgspec-0.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9714b78965047638c01c818b4b418133d77e849017de17b0655ee37b714b47a6"}, - {file = "msgspec-0.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:241277eed9fd91037372519fca62aecf823f7229c1d351030d0be5e3302580c1"}, - {file = "msgspec-0.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d08175cbb55c1a87dd258645dce6cd00705d6088bf88e7cf510a9d5c24b0720b"}, - {file = "msgspec-0.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:da13a06e77d683204eee3b134b08ecd5e4759a79014027b1bcd7a12c614b466d"}, - {file = "msgspec-0.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73e70217ff5e4ac244c8f1b0769215cbc81e1c904e135597a5b71162857e6c27"}, - {file = "msgspec-0.18.4-cp310-cp310-win_amd64.whl", hash = "sha256:dc25e6100026f5e1ecb5120150f4e78beb909cbeb0eb724b9982361b75c86c6b"}, - {file = "msgspec-0.18.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e14287c3405093645b3812e3436598edd383b9ed724c686852e65d569f39f953"}, - {file = "msgspec-0.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acdcef2fccfff02f80ac8673dbeab205c288b680d81e05bfb5ae0be6b1502a7e"}, - {file = "msgspec-0.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b052fd7d25a8aa2ffde10126ee1d97b4c6f3d81f3f3ab1258ff759a2bd794874"}, - {file = "msgspec-0.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826dcb0dfaac0abbcf3a3ae991749900671796eb688b017a69a82bde1e624662"}, - {file = "msgspec-0.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:86800265f87f192a0daefe668e0a9634c35bf8af94b1f297e1352ac62d2e26da"}, - {file = "msgspec-0.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:227fee75a25080a8b3677cdd95b9c0c3652e27869004a084886c65eb558b3dd6"}, - {file = "msgspec-0.18.4-cp311-cp311-win_amd64.whl", hash = "sha256:828ef92f6654915c36ef6c7d8fec92404a13be48f9ff85f060e73b30299bafe1"}, - {file = "msgspec-0.18.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8476848f4937da8faec53700891694df2e412453cb7445991f0664cdd1e2dd16"}, - {file = "msgspec-0.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f668102958841c5bbd3ba7cf569a65d17aa3bdcf22124f394dfcfcf53cc5a9b9"}, - {file = "msgspec-0.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc2405dba5af6478dedd3512bb92197b6f9d1bc0095655afbe9b54d7a426f19f"}, - {file = "msgspec-0.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99f3c13569a5add0980b0d8c6e0bd94a656f6363b26107435b3091df979d228"}, - {file = "msgspec-0.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a198409f672f93534c9c36bdc9eea9fb536827bd63ea846882365516a961356"}, - {file = "msgspec-0.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e21bc5aae6b80dfe4eb75dc1bb29af65483f967d5522e9e3812115a0ba285cac"}, - {file = "msgspec-0.18.4-cp312-cp312-win_amd64.whl", hash = "sha256:44d551aee1ec8aa2d7b64762557c266bcbf7d5109f2246955718d05becc509d6"}, - {file = "msgspec-0.18.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bbbc08d59f74de5791bda63569f26a35ae1dd6bd20c55c3ceba5567b0e5a8ef1"}, - {file = "msgspec-0.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87bc01949a35970398f5267df8ed4189c340727bb6feec99efdb9969dd05cf30"}, - {file = "msgspec-0.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96ccaef83adc0ce96d95328a03289cd5aead4fe400aac21fbe2008855a124a01"}, - {file = "msgspec-0.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6229dd49438d81ed7a3470e3cbc9646b1cc1b120d415a1786df880dabb1d1c4"}, - {file = "msgspec-0.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:55e578fd921c88de0d3a209fe5fd392bb66623924c6525b42cea37c72bf8d558"}, - {file = "msgspec-0.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e95bd0a946b5b7206f27c0f654f490231c9ad5e5a4ff65af8c986f5114dfaf0e"}, - {file = "msgspec-0.18.4-cp38-cp38-win_amd64.whl", hash = "sha256:7e95817021db96c43fd81244228e185b13b085cca3d5169af4e2dfe3ff412954"}, - {file = "msgspec-0.18.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:847d79f6f0b698671ff390aa5a66e207108f2c23b077ef9314ca4fe7819fa4ec"}, - {file = "msgspec-0.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4294158c233884f3b3220f0e96a30d3e916a4781f9502ae6d477bd57bbc80ad"}, - {file = "msgspec-0.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb11ba2709019192636042df5c8db8738e45946735627021b7e7934714526e4"}, - {file = "msgspec-0.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b01efbf80a987a99e9079257c893c026dc661d4cd05caa1f7eabf4accc7f1fbc"}, - {file = "msgspec-0.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:991aa3c76d1b1ec84e840d0b3c96692af834e1f8a1e1a3974cbd189eaf0f2276"}, - {file = "msgspec-0.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8064908ddb3d95d3261aaca48fd38abb16ccf59dc3f2d01eb4e04591fc1e9bd4"}, - {file = "msgspec-0.18.4-cp39-cp39-win_amd64.whl", hash = "sha256:5f446f16ea57d70cceec29b7cb85ec0b3bea032e3dec316806e38575ea3a69b4"}, - {file = "msgspec-0.18.4.tar.gz", hash = "sha256:cb62030bd6b1a00b01a2fcb09735016011696304e6b1d3321e58022548268d3e"}, -] - -[package.extras] -dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"] -doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] -test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"] -toml = ["tomli", "tomli-w"] -yaml = ["pyyaml"] - -[[package]] -name = "multidict" -version = "6.0.4" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -2442,40 +1749,6 @@ files = [ [package.dependencies] setuptools = "*" -[[package]] -name = "numpy" -version = "1.25.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, -] - [[package]] name = "packaging" version = "23.2" @@ -2487,53 +1760,6 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pandas" -version = "1.5.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, - {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, - {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, - {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, - {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, - {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, - {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, - {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, - {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, - {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, - {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, - {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, - {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, - {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, - {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, - {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, -] -python-dateutil = ">=2.8.1" -pytz = ">=2020.1" - -[package.extras] -test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] - [[package]] name = "parsimonious" version = "0.9.0" @@ -2760,28 +1986,6 @@ files = [ [package.dependencies] wcwidth = "*" -[[package]] -name = "protobuf" -version = "4.24.4" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, - {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, - {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, - {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, - {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, - {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, - {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, - {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, - {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, - {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, - {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, -] - [[package]] name = "ptyprocess" version = "0.7.0" @@ -2818,24 +2022,6 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -[[package]] -name = "py-cid" -version = "0.3.0" -description = "Self-describing content-addressed identifiers for distributed systems" -optional = false -python-versions = "*" -files = [ - {file = "py-cid-0.3.0.tar.gz", hash = "sha256:22f432cc6fb68d12a9c35dbdc92c95484fc49e31dfcb9e0efb0082233c5394e3"}, - {file = "py_cid-0.3.0-py2.py3-none-any.whl", hash = "sha256:7c48a6ee0bc50fd114d4b24849cd689a31d3ad5bdf8fa073bf68f846fd58c5da"}, -] - -[package.dependencies] -base58 = ">=1.0.2,<2.0" -morphys = ">=1.0,<2.0" -py-multibase = ">=1.0.0,<2.0.0" -py-multicodec = "<0.3.0" -py-multihash = ">=0.2.0,<1.0.0" - [[package]] name = "py-ecc" version = "6.0.0" @@ -2891,75 +2077,6 @@ eth-extra = ["blake2b-py (>=0.1.4,<0.2)", "coincurve (>=13.0.0,<14.0.0)", "eth-h lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==1.4.0)", "pydocstyle (>=6.0.0)", "types-setuptools"] test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)"] -[[package]] -name = "py-geth" -version = "3.13.0" -description = "py-geth: Run Go-Ethereum as a subprocess" -optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "py-geth-3.13.0.tar.gz", hash = "sha256:f3563e2de8e78599cb9c69ee5bf3bded858ac6cf59891a04177f2353c425fdb7"}, - {file = "py_geth-3.13.0-py3-none-any.whl", hash = "sha256:1eb9c1d05b51133a6961889ec508cdcb19d24d32888704c4e034cae86a3accad"}, -] - -[package.dependencies] -semantic-version = ">=2.6.0" - -[package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flaky (>=3.2.0)", "importlib-metadata (<5)", "ipython", "isort (>=5.10.1)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "requests (>=2.20)", "towncrier (>=21,<22)", "tox (>=3.28.0)", "twine", "wheel"] -docs = ["towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "importlib-metadata (<5)", "isort (>=5.10.1)"] -test = ["flaky (>=3.2.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] - -[[package]] -name = "py-multibase" -version = "1.0.3" -description = "Multibase implementation for Python" -optional = false -python-versions = "*" -files = [ - {file = "py-multibase-1.0.3.tar.gz", hash = "sha256:d28a20efcbb61eec28f55827a0bf329c7cea80fffd933aecaea6ae8431267fe4"}, - {file = "py_multibase-1.0.3-py2.py3-none-any.whl", hash = "sha256:2677c1fafcc0ae15ddb9c7f444c5becc2530b3889124fd4fa2959ddfefb8c15b"}, -] - -[package.dependencies] -morphys = ">=1.0,<2.0" -python-baseconv = ">=1.2.0,<2.0" -six = ">=1.10.0,<2.0" - -[[package]] -name = "py-multicodec" -version = "0.2.1" -description = "Multicodec implementation in Python" -optional = false -python-versions = "*" -files = [ - {file = "py-multicodec-0.2.1.tar.gz", hash = "sha256:83021ffe8c0e272d19b5b86bc5b39efa67c8e9f4735ce6cafdbc1ace767ec647"}, - {file = "py_multicodec-0.2.1-py2.py3-none-any.whl", hash = "sha256:55b6bb53088a63e56c434cb11b29795e8805652bac43d50a8f2a9bcf5ca84e1f"}, -] - -[package.dependencies] -morphys = ">=1.0,<2.0" -six = ">=1.10.0,<2.0" -varint = ">=1.0.2,<2.0.0" - -[[package]] -name = "py-multihash" -version = "0.2.3" -description = "Multihash implementation in Python" -optional = false -python-versions = "*" -files = [ - {file = "py-multihash-0.2.3.tar.gz", hash = "sha256:f0ade4de820afdc4b4aaa40464ec86c9da5cae3a4578cda2daab4b0eb7e5b18d"}, - {file = "py_multihash-0.2.3-py2.py3-none-any.whl", hash = "sha256:a0602c99093587dfbf1634e2e8c7726de39374b0d68587a36093b4c237af6969"}, -] - -[package.dependencies] -base58 = ">=1.0.2,<2.0" -morphys = ">=1.0,<2.0" -six = ">=1.10.0,<2.0" -varint = ">=1.0.2,<2.0" - [[package]] name = "pycodestyle" version = "2.8.0" @@ -3023,143 +2140,6 @@ files = [ {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, ] -[[package]] -name = "pydantic" -version = "2.4.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, - {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.10.1" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.10.1" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, - {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, - {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, - {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, - {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, - {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, - {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, - {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, - {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, - {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, - {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, - {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, - {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, - {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, - {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, - {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - [[package]] name = "pyethash" version = "0.1.27" @@ -3181,23 +2161,6 @@ files = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] -[[package]] -name = "pygithub" -version = "1.59.1" -description = "Use the full Github API v3" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyGithub-1.59.1-py3-none-any.whl", hash = "sha256:3d87a822e6c868142f0c2c4bf16cce4696b5a7a4d142a7bd160e1bdf75bc54a9"}, - {file = "PyGithub-1.59.1.tar.gz", hash = "sha256:c44e3a121c15bf9d3a5cc98d94c9a047a5132a9b01d22264627f58ade9ddc217"}, -] - -[package.dependencies] -deprecated = "*" -pyjwt = {version = ">=2.4.0", extras = ["crypto"]} -pynacl = ">=1.4.0" -requests = ">=2.14.0" - [[package]] name = "pygments" version = "2.16.1" @@ -3212,52 +2175,6 @@ files = [ [package.extras] plugins = ["importlib-metadata"] -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.dependencies] -cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - [[package]] name = "pyproject-hooks" version = "1.0.0" @@ -3363,74 +2280,6 @@ psutil = ["psutil (>=3.0)"] setproctitle = ["setproctitle"] testing = ["filelock"] -[[package]] -name = "python-baseconv" -version = "1.2.2" -description = "Convert numbers from base 10 integers to base X strings and back again." -optional = false -python-versions = "*" -files = [ - {file = "python-baseconv-1.2.2.tar.gz", hash = "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b"}, -] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2023.3.post1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, -] - -[[package]] -name = "pyunormalize" -version = "15.0.0" -description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyunormalize-15.0.0.tar.gz", hash = "sha256:e63fdba0d85ea04579dde2fc29a072dba773dcae600b04faf6cc90714c8b1302"}, -] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - [[package]] name = "pywin32-ctypes" version = "0.2.2" @@ -3915,16 +2764,6 @@ files = [ {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, ] -[[package]] -name = "safe-pysha3" -version = "1.0.4" -description = "SHA-3 (Keccak) for Python 3.9 - 3.11" -optional = false -python-versions = "*" -files = [ - {file = "safe-pysha3-1.0.4.tar.gz", hash = "sha256:e429146b1edd198b2ca934a2046a65656c5d31b0ec894bbd6055127f4deaff17"}, -] - [[package]] name = "secretstorage" version = "3.3.3" @@ -3940,21 +2779,6 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" -[[package]] -name = "semantic-version" -version = "2.10.0" -description = "A library implementing the 'SemVer' scheme." -optional = false -python-versions = ">=2.7" -files = [ - {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, - {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, -] - -[package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] -doc = ["Sphinx", "sphinx-rtd-theme"] - [[package]] name = "setuptools" version = "68.2.2" @@ -4004,92 +2828,6 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] -[[package]] -name = "sqlalchemy" -version = "2.0.22" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"}, - {file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, - {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, - {file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"}, - {file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} -typing-extensions = ">=4.2.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] - [[package]] name = "stack-data" version = "0.6.3" @@ -4172,26 +2910,6 @@ files = [ {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, ] -[[package]] -name = "tqdm" -version = "4.66.1" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - [[package]] name = "traitlets" version = "5.11.2" @@ -4269,16 +2987,6 @@ brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotl secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "varint" -version = "1.0.2" -description = "Simple python varint implementation" -optional = false -python-versions = "*" -files = [ - {file = "varint-1.0.2.tar.gz", hash = "sha256:a6ecc02377ac5ee9d65a6a8ad45c9ff1dac8ccee19400a5950fb51d594214ca5"}, -] - [[package]] name = "virtualenv" version = "20.24.5" @@ -4324,45 +3032,6 @@ docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.7" -files = [ - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, - {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, - {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, - {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, - {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, - {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, - {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, - {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, - {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - [[package]] name = "wcwidth" version = "0.2.8" @@ -4374,43 +3043,6 @@ files = [ {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, ] -[[package]] -name = "web3" -version = "6.11.0" -description = "web3.py" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "web3-6.11.0-py3-none-any.whl", hash = "sha256:44e79da6a4765eacf137f2f388e37aa0c1e24a93bdfb462cffe9441d1be3d509"}, - {file = "web3-6.11.0.tar.gz", hash = "sha256:050dea52ae73d787272e7ecba7249f096595938c90cce1a384c20375c6b0f720"}, -] - -[package.dependencies] -aiohttp = ">=3.7.4.post0" -eth-abi = ">=4.0.0" -eth-account = ">=0.8.0" -eth-hash = {version = ">=0.5.1", extras = ["pycryptodome"]} -eth-tester = {version = "v0.9.1-b.1", extras = ["py-evm"], optional = true, markers = "extra == \"tester\""} -eth-typing = ">=3.0.0" -eth-utils = ">=2.1.0" -hexbytes = ">=0.1.0" -jsonschema = ">=4.0.0" -lru-dict = ">=1.1.6" -protobuf = ">=4.21.6" -py-geth = {version = ">=3.11.0", optional = true, markers = "extra == \"tester\""} -pyunormalize = ">=15.0.0" -pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} -requests = ">=2.16.0" -typing-extensions = ">=4.0.1" -websockets = ">=10.0.0" - -[package.extras] -dev = ["black (>=22.1.0)", "build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (==v0.9.1-b.1)", "flake8 (==3.8.3)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "isort (>=5.11.0)", "mypy (==1.4.1)", "py-geth (>=3.11.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.18.1)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)", "when-changed (>=0.3.0)"] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -ipfs = ["ipfshttpclient (==0.8.0a2)"] -linter = ["black (>=22.1.0)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==1.4.1)", "types-protobuf (==3.19.13)", "types-requests (>=2.26.1)", "types-setuptools (>=57.4.4)"] -tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] - [[package]] name = "webencodings" version = "0.5.1" @@ -4422,85 +3054,6 @@ files = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] -[[package]] -name = "websockets" -version = "11.0.3" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, - {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, - {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, - {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, - {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, - {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, - {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, - {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, - {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, - {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, - {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, - {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, - {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, - {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, -] - [[package]] name = "wheel" version = "0.41.2" @@ -4532,90 +3085,6 @@ attrs = "*" [package.extras] test = ["pytest"] -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, -] - [[package]] name = "xattr" version = "0.10.1" @@ -4700,93 +3169,6 @@ files = [ [package.dependencies] cffi = ">=1.0" -[[package]] -name = "yarl" -version = "1.9.2" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - [[package]] name = "zipp" version = "3.17.0" @@ -4805,4 +3187,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "1e7274a173b8da3e549cdcf97685b564373a0517b39e5465f442c9e2a02e1aee" +content-hash = "852e23bdada5987fdd4245dafa312bea5a6cfd683e2afe5aece58bdc9ff973ea" diff --git a/pyproject.toml b/pyproject.toml index a4d73cbd..b6144268 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ black = "22.3.0" flake8 = "4.0.1" isort = "5.12.0" mamushi = "^0.0.2a1" -eth-ape = "^0.6.18" [tool.poetry.group.testing.dependencies] diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index ab06045e..57645a6f 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -23,8 +23,8 @@ "ethereum:mainnet": { "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", - "plain_amm": "0x933f4769DCC27fC7345D9d5975AE48EC4D0F829C", - "meta_amm": "0xDD7EBB1C49780519dD9755B8B1A23a6f42CE099E", + "plain_amm": "", + "meta_amm": "", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", }, @@ -32,36 +32,36 @@ "arbitrum:mainnet": { "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", - "plain_amm": "0x0458ea5f4cd00e873264be2031ceb8f9d9b3116c", - "meta_amm": "0xc6c09471ee39c7e30a067952fcc89c8922f9ab53", + "plain_amm": "", + "meta_amm": "", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", - "meta_amm": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", - "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", + "plain_amm": "", + "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, "linea:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", - "meta_amm": "0x604388bb1159afd21eb5191ce22b4decdee2ae22", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", - "meta_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "zksync:mainnet": { @@ -74,65 +74,65 @@ "pzkevm:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x87fe17697d0f14a222e8bef386a0860ecffdd617", - "plain_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", - "meta_amm": "0x06452f9c013fc37169B57Eab8F50A7A48c9198A3", + "plain_amm": "", + "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xc9Fe0C63Af9A39402e8a5514f9c43Af0322b665F", - "meta_amm": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", + "plain_amm": "", + "meta_amm": "", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "polygon:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", - "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "avax:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", - "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "ftm:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "plain_amm": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "meta_amm": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "plain_amm": "", + "meta_amm": "", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "bsc:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "plain_amm": "0x64379C265Fc6595065D7d835AAaa731c0584dB80", - "meta_amm": "0xd3B17f862956464ae4403cCF829CE69199856e1e", + "plain_amm": "", + "meta_amm": "", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "celo:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", - "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "plain_amm": "0xa7Ba18EeFcD9513230987eC2faB6711AF5AbD9c2", - "meta_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", + "plain_amm": "", + "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "plain_amm": "0x7C2085419BE6a04f4ad88ea91bC9F5C6E6C463D8", - "meta_amm": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + "plain_amm": "", + "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "tron:mainnet": { diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py index 4674ac0a..78c22783 100644 --- a/tests/pools/test_virtual_price.py +++ b/tests/pools/test_virtual_price.py @@ -64,7 +64,7 @@ def test_exchange(bob, swap, sending, receiving, decimals): assert swap.get_virtual_price() > virtual_price -def test_pump_virtual_price(bob, swap, pool_tokens, initial_amounts, pool_size): +def test_donate_virtual_price(bob, swap, pool_tokens, initial_amounts, pool_size): # make a deposit for i, amount in enumerate(initial_amounts): From db58dd62e92b2b9b9d6cc9079f0de67cfcc89a95 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:06:31 +0100 Subject: [PATCH 248/337] reduce unnecessary storage write if need be --- contracts/main/CurveStableSwapMetaNG.vy | 3 +-- contracts/main/CurveStableSwapNG.vy | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 68200bf2..ff1ece61 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1033,8 +1033,7 @@ def remove_liquidity( if ma_last_time_unpacked[1] < block.timestamp: ma_last_time_unpacked[1] = block.timestamp - - self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) # ------------------------------- Log event ------------------------------ diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 760c6b21..4fe4745d 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -841,8 +841,7 @@ def remove_liquidity( if ma_last_time_unpacked[1] < block.timestamp: ma_last_time_unpacked[1] = block.timestamp - - self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) # ------------------------------- Log event ------------------------------ From 4511734af0ab458102a807cd1883682e3780c0f4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:25:24 +0100 Subject: [PATCH 249/337] set oracles (now called rate_oracles to differentiate between price and rate oracles) as immutable; for meta, set several internal params as uint256 immutables instead of static arrays; remove unused test --- contracts/main/CurveStableSwapMetaNG.vy | 38 +++++++++++-------------- contracts/main/CurveStableSwapNG.vy | 13 +++++---- tests/pools/meta/test_meta.py | 10 ------- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index ff1ece61..6904ed81 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -201,7 +201,7 @@ BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) -asset_types: immutable(uint8[N_COINS]) +asset_type: immutable(uint8) stored_balances: uint256[N_COINS] # Fee specific vars @@ -229,9 +229,9 @@ admin_balances: public(DynArray[uint256, MAX_COINS]) # ----------------------- Oracle Specific vars ------------------------------- -rate_multipliers: immutable(uint256[N_COINS]) +rate_multiplier: immutable(uint256) # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: uint256[N_COINS] +rate_oracle: immutable(uint256) # this is the rate oracle for the token at 0th index # For ERC4626 tokens, we need: call_amount: immutable(uint256) @@ -327,8 +327,9 @@ def __init__( BASE_COINS = _base_coins BASE_N_COINS = len(_base_coins) coins = _coins # <---------------- coins[1] is always base pool LP token. - asset_types = [_asset_types[0], _asset_types[1]] - rate_multipliers = [_rate_multipliers[0], _rate_multipliers[1]] + + asset_type = _asset_types[0] + rate_multiplier = _rate_multipliers[0] for i in range(MAX_COINS): if i < BASE_N_COINS: @@ -341,7 +342,7 @@ def __init__( ) # For ERC4626 tokens: - if asset_types[0] == 3: + if asset_type == 3: # In Vyper 0.3.10, if immutables are not set, because of an if-statement, # it is by default set to 0; this is fine in the case of these two # immutables, since they are only used if asset_types[0] == 3. @@ -363,16 +364,12 @@ def __init__( self.D_ma_time = 62324 # <--------- 12 hours default on contract start. self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) - # ------------------- initialize storage for DynArrays ------------------ - self.last_prices_packed = self.pack_2(10**18, 10**18) - for i in range(N_COINS_128): - - self.oracles[i] = convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256) - self.admin_balances = [0, 0] self.stored_balances = [0, 0] + rate_oracle = convert(_method_ids[0], uint256) * 2**224 | convert(_oracles[0], uint256) + # --------------------------- ERC20 stuff ---------------------------- name = _name @@ -480,7 +477,7 @@ def _transfer_out( @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ - if asset_types[0] != 2: + if asset_type != 2: assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True @@ -508,16 +505,15 @@ def _stored_rates() -> uint256[N_COINS]: this method queries that rate by static-calling an external contract. """ - rates: uint256[N_COINS] = [rate_multipliers[0], StableSwap(BASE_POOL).get_virtual_price()] - oracles: uint256[N_COINS] = self.oracles + rates: uint256[N_COINS] = [rate_multiplier, StableSwap(BASE_POOL).get_virtual_price()] - if asset_types[0] == 1 and not self.oracles[0] == 0: + if asset_type == 1 and not rate_oracle == 0: # NOTE: fetched_rate is assumed to be 10**18 precision fetched_rate: uint256 = convert( raw_call( - convert(oracles[0] % 2**160, address), - _abi_encode(oracles[0] & ORACLE_BIT_MASK), + convert(rate_oracle % 2**160, address), + _abi_encode(rate_oracle & ORACLE_BIT_MASK), max_outsize=32, is_static_call=True, ), @@ -527,7 +523,7 @@ def _stored_rates() -> uint256[N_COINS]: # rates[0] * fetched_rate / PRECISION rates[0] = unsafe_div(rates[0] * fetched_rate, PRECISION) - elif asset_types[0] == 3: # ERC4626 + elif asset_type == 3: # ERC4626 # rates[0] * fetched_rate / PRECISION rates[0] = unsafe_div( @@ -553,7 +549,7 @@ def _balances() -> uint256[N_COINS]: admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128): - if 2 in asset_types: + if asset_type != 2: result[i] = ERC20(coins[i]).balanceOf(self) - admin_balances[i] else: result[i] = self.stored_balances[i] - admin_balances[i] @@ -616,7 +612,7 @@ def exchange_received( @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received """ - assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens + assert asset_type != 2 # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 4fe4745d..bea9c68c 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -184,7 +184,7 @@ admin_balances: public(DynArray[uint256, MAX_COINS]) rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) # [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: DynArray[uint256, MAX_COINS] +rate_oracles: immutable(DynArray[uint256, MAX_COINS]) # For ERC4626 tokens, we need: call_amount: immutable(DynArray[uint256, MAX_COINS]) @@ -293,6 +293,7 @@ def __init__( _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + _rate_oracles: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) for i in range(MAX_COINS_128): if i == N_COINS_128: @@ -301,7 +302,7 @@ def __init__( if i < N_COINS_128 - 1: self.last_prices_packed.append(self.pack_2(10**18, 10**18)) - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) + _rate_oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) self.stored_balances.append(0) self.admin_balances.append(0) @@ -318,6 +319,7 @@ def __init__( call_amount = _call_amount scale_factor = _scale_factor + rate_oracles = _rate_oracles # ----------------------------- ERC20 stuff ------------------------------ @@ -428,20 +430,19 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: contract. """ rates: DynArray[uint256, MAX_COINS] = rate_multipliers - oracles: DynArray[uint256, MAX_COINS] = self.oracles for i in range(MAX_COINS_128): if i == N_COINS_128: break - if asset_types[i] == 1 and not oracles[i] == 0: + if asset_types[i] == 1 and not rate_oracles[i] == 0: # NOTE: fetched_rate is assumed to be 10**18 precision fetched_rate: uint256 = convert( raw_call( - convert(oracles[i] % 2**160, address), - _abi_encode(oracles[i] & ORACLE_BIT_MASK), + convert(rate_oracles[i] % 2**160, address), + _abi_encode(rate_oracles[i] & ORACLE_BIT_MASK), max_outsize=32, is_static_call=True, ), diff --git a/tests/pools/meta/test_meta.py b/tests/pools/meta/test_meta.py index 8042b024..4e818da5 100644 --- a/tests/pools/meta/test_meta.py +++ b/tests/pools/meta/test_meta.py @@ -170,13 +170,3 @@ def test_exchange(swap, charlie, meta_token): mint_for_testing(charlie, amount, meta_token, False) meta_token.approve(swap.address, 2**256 - 1, sender=charlie) swap.exchange(0, 1, amount, 0, sender=charlie) - - -def test_exchange_underlying(swap, charlie, meta_token, base_pool_tokens): - - base_pool_index = 0 - amount = 1000 * 10**18 - mint_for_testing(charlie, amount, meta_token, False) - mint_for_testing(charlie, amount, base_pool_tokens[base_pool_index], False) - - swap.exchange_underlying(1, 0) From ba4343c083ea36108e07b7dcc1e53dc8a3063a14 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:09:46 +0100 Subject: [PATCH 250/337] match unsafe math in non meta impl --- contracts/main/CurveStableSwapMetaNG.vy | 6 +- contracts/main/CurveStableSwapNG.vy | 100 ++++++++++++++---------- 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 6904ed81..74c8da5a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -926,7 +926,7 @@ def remove_liquidity_imbalance( D1: uint256 = self.get_D_mem(rates, new_balances, amp) # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - base_fee: uint256 = unsafe_div(self.fee * N_COINS, 4) + base_fee: uint256 = unsafe_div(unsafe_mul(self.fee, N_COINS), 4) # ys: uint256 = (D0 + D1) / N_COINS ys: uint256 = unsafe_div(D0 + D1, N_COINS) @@ -1277,11 +1277,11 @@ def _calc_withdraw_one_coin( D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = math.get_y_D(amp, i, [xp[0], xp[1]], D1, N_COINS) + base_fee: uint256 = unsafe_div(unsafe_mul(self.fee, N_COINS), 4) xp_reduced: uint256[N_COINS] = xp # ys: uint256 = (D0 + D1) / (2 * N_COINS) - ys: uint256 = unsafe_div((D0 + D1), unsafe_mul(2, N_COINS)) + ys: uint256 = unsafe_div((D0 + D1), 4) # base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - base_fee: uint256 = unsafe_div(unsafe_mul(self.fee, N_COINS), 4) dx_expected: uint256 = 0 xp_j: uint256 = 0 diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index bea9c68c..e8f76fdf 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -278,7 +278,7 @@ def __init__( factory = Factory(msg.sender) - A: uint256 = _A * A_PRECISION + A: uint256 = unsafe_mul(_A, A_PRECISION) self.initial_A = A self.future_A = A self.fee = _fee @@ -621,12 +621,15 @@ def add_liquidity( difference: uint256 = 0 new_balance: uint256 = 0 - ys: uint256 = (D0 + D1) / N_COINS + ys: uint256 = unsafe_div((D0 + D1), N_COINS) xs: uint256 = 0 _dynamic_fee_i: uint256 = 0 # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) + base_fee: uint256 = unsafe_div( + unsafe_mul(self.fee, N_COINS), + unsafe_mul(4, unsafe_sub(N_COINS, 1)) + ) for i in range(MAX_COINS_128): @@ -638,20 +641,20 @@ def add_liquidity( new_balance = new_balances[i] if ideal_balance > new_balance: - difference = ideal_balance - new_balance + difference = unsafe_sub(ideal_balance, new_balance) else: - difference = new_balance - ideal_balance + difference = unsafe_sub(new_balance, ideal_balance) # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) - fees.append(_dynamic_fee_i * difference / FEE_DENOMINATOR) - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR + fees.append(unsafe_div(_dynamic_fee_i * difference, FEE_DENOMINATOR)) + self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) new_balances[i] -= fees[i] xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) D1 = self.get_D(xp, amp) # <--------------- Reuse D1 for new D value. - mint_amount = total_supply * (D1 - D0) / D0 + mint_amount = unsafe_div(total_supply * (D1 - D0), D0) self.upkeep_oracles(xp, amp, D1) else: @@ -700,7 +703,7 @@ def remove_liquidity_one_coin( dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i) assert dy >= _min_received, "Not enough coins removed" - self.admin_balances[i] += fee * admin_fee / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fee * admin_fee, FEE_DENOMINATOR) self._burnFrom(msg.sender, _burn_amount) @@ -743,8 +746,11 @@ def remove_liquidity_imbalance( self._transfer_out(i, _amounts[i], _receiver) D1: uint256 = self.get_D_mem(rates, new_balances, amp) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - ys: uint256 = (D0 + D1) / N_COINS + base_fee: uint256 = unsafe_div( + unsafe_mul(self.fee, N_COINS), + unsafe_mul(4, unsafe_sub(N_COINS, 1)) + ) + ys: uint256 = unsafe_div((D0 + D1), N_COINS) fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) dynamic_fee: uint256 = 0 @@ -763,22 +769,22 @@ def remove_liquidity_imbalance( new_balance = new_balances[i] if ideal_balance > new_balance: - difference = ideal_balance - new_balance + difference = unsafe_sub(ideal_balance, new_balance) else: - difference = new_balance - ideal_balance + difference = unsafe_sub(new_balance, ideal_balance) xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees.append(dynamic_fee * difference / FEE_DENOMINATOR) + fees.append(unsafe_div(dynamic_fee * difference, FEE_DENOMINATOR)) - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR + self.admin_balances[i] += unsafe_div(fees[i] * admin_fee, FEE_DENOMINATOR) new_balances[i] -= fees[i] D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. self.upkeep_oracles(self._xp_mem(rates, new_balances), amp, D1) total_supply: uint256 = self.total_supply - burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 + burn_amount: uint256 = unsafe_div((D0 - D1) * total_supply, D0) + 1 assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" @@ -818,7 +824,7 @@ def remove_liquidity( if i == N_COINS_128: break - value = balances[i] * _burn_amount / total_supply + value = unsafe_div(balances[i] * _burn_amount, total_supply) assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" amounts.append(value) self._transfer_out(i, value, _receiver) @@ -850,7 +856,7 @@ def remove_liquidity( msg.sender, amounts, empty(DynArray[uint256, MAX_COINS]), - total_supply - _burn_amount + unsafe_sub(total_supply, _burn_amount) ) # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- @@ -880,9 +886,12 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: return _fee xps2: uint256 = (xpi + xpj) ** 2 - return ( - (_offpeg_fee_multiplier * _fee) / - ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) + return unsafe_div( + unsafe_mul(_offpeg_fee_multiplier, _fee), + unsafe_add( + unsafe_sub(_offpeg_fee_multiplier, FEE_DENOMINATOR) * 4 * xpi * xpj / xps2, + FEE_DENOMINATOR + ) ) @@ -900,14 +909,20 @@ def __exchange( y: uint256 = self.get_y(i, j, x, _xp, amp, D) dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR + dy_fee: uint256 = unsafe_div( + dy * self._dynamic_fee( + unsafe_div(_xp[i] + x, 2), unsafe_div(_xp[j] + y, 2), self.fee + ), + FEE_DENOMINATOR + ) # Convert all to real units dy = (dy - dy_fee) * PRECISION / rates[j] - self.admin_balances[j] += ( - dy_fee * admin_fee / FEE_DENOMINATOR - ) * PRECISION / rates[j] + self.admin_balances[j] += unsafe_div( + unsafe_div(dy_fee * admin_fee, FEE_DENOMINATOR) * PRECISION, + rates[j] + ) # Calculate and store state prices: xp: DynArray[uint256, MAX_COINS] = _xp @@ -949,7 +964,7 @@ def _exchange( # ------------------------------- Exchange ------------------------------- - x: uint256 = xp[i] + dx * rates[i] / PRECISION + x: uint256 = unsafe_div(xp[i] + dx * rates[i], PRECISION) dy: uint256 = self.__exchange(x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" @@ -1178,9 +1193,9 @@ def _A() -> uint256: t0: uint256 = self.initial_A_time # Expressions in uint256 cannot have negative numbers, thus "if" if A1 > A0: - return A0 + (A1 - A0) * (block.timestamp - t0) / (t1 - t0) + return A0 + unsafe_sub(A1, A0) * (block.timestamp - t0) / (t1 - t0) else: - return A0 - (A0 - A1) * (block.timestamp - t0) / (t1 - t0) + return A0 - unsafe_sub(A0, A1) * (block.timestamp - t0) / (t1 - t0) else: # when t1 == 0 or block.timestamp >= t1 return A1 @@ -1197,7 +1212,7 @@ def _xp_mem( for i in range(MAX_COINS_128): if i == N_COINS_128: break - result.append(_rates[i] * _balances[i] / PRECISION) + result.append(unsafe_div(_rates[i] * _balances[i], PRECISION)) return result @@ -1236,9 +1251,12 @@ def _calc_withdraw_one_coin( D1: uint256 = D0 - _burn_amount * D0 / total_supply new_y: uint256 = self.get_y_D(amp, i, xp, D1) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - ys: uint256 = (D0 + D1) / (2 * N_COINS) + base_fee: uint256 = unsafe_div( + unsafe_mul(self.fee, N_COINS), + unsafe_mul(4, unsafe_sub(N_COINS, 1)) + ) xp_reduced: DynArray[uint256, MAX_COINS] = xp + ys: uint256 = unsafe_div((D0 + D1), unsafe_mul(2, N_COINS)) dx_expected: uint256 = 0 xp_j: uint256 = 0 @@ -1255,17 +1273,17 @@ def _calc_withdraw_one_coin( if j == i: dx_expected = xp_j * D1 / D0 - new_y - xavg = (xp_j + new_y) / 2 + xavg = unsafe_div((xp_j + new_y), 2) else: dx_expected = xp_j - xp_j * D1 / D0 xavg = xp_j dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) - xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR + xp_reduced[j] = unsafe_div(xp_j - dynamic_fee * dx_expected, FEE_DENOMINATOR) dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors + dy = unsafe_div((dy - 1) * PRECISION, rates[i]) # Withdraw less to account for rounding errors # update xp with new_y for p calculations. xp[i] = new_y @@ -1309,14 +1327,14 @@ def _get_p( Dr = Dr * D / xp[i] p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - xp0_A: uint256 = ANN * xp[0] / A_PRECISION + xp0_A: uint256 = unsafe_div(ANN * xp[0], A_PRECISION) for i in range(1, MAX_COINS): if i == N_COINS: break - p.append(10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr)) + p.append(10**18 * unsafe_div(xp0_A + Dr * xp[0], xp[i]) / (xp0_A + Dr)) return p @@ -1387,10 +1405,10 @@ def _calc_moving_average( if ma_last_time < block.timestamp: # calculate new_ema_value and return that. alpha: uint256 = self.exp( -convert( - (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 + unsafe_div(unsafe_mul(unsafe_sub(block.timestamp, ma_last_time), 10**18), averaging_window), int256 ) ) - return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 + return unsafe_div(last_spot_value * (10**18 - alpha) + last_ema_value * alpha, 10**18) return last_ema_value @@ -1647,7 +1665,7 @@ def permit( assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner self.allowance[_owner][_spender] = _value - self.nonces[_owner] = nonce + 1 + self.nonces[_owner] = unsafe_add(nonce, 1) log Approval(_owner, _spender, _value) return True @@ -1756,7 +1774,7 @@ def calc_token_amount( @view @external def A() -> uint256: - return self._A() / A_PRECISION + return unsafe_div(self._A(), A_PRECISION) @view @@ -1864,7 +1882,7 @@ def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) """ assert msg.sender == factory.admin() # dev: only owner - assert 0 not in [_ma_exp_time, _D_ma_time] + assert unsafe_mul(_ma_exp_time, _D_ma_time) > 0 # dev: 0 in input values self.ma_exp_time = _ma_exp_time self.D_ma_time = _D_ma_time From f6622ffdba3c3e58f1831c6b4c9ae369ff77965a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:48:37 +0100 Subject: [PATCH 251/337] log event when ma exp time is changed --- contracts/main/CurveStableSwapMetaNG.vy | 6 ++++++ contracts/main/CurveStableSwapNG.vy | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 74c8da5a..ceca3ab2 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -183,6 +183,10 @@ event ApplyNewFee: fee: uint256 offpeg_fee_multiplier: uint256 +event SetNewMATime: + ma_exp_time: uint256 + D_ma_time: uint256 + MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 @@ -1857,3 +1861,5 @@ def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): self.ma_exp_time = _ma_exp_time self.D_ma_time = _D_ma_time + + log SetNewMATime(_ma_exp_time, _D_ma_time) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index e8f76fdf..69fbc66e 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -142,6 +142,10 @@ event ApplyNewFee: fee: uint256 offpeg_fee_multiplier: uint256 +event SetNewMATime: + ma_exp_time: uint256 + D_ma_time: uint256 + MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory MAX_COINS_128: constant(int128) = 8 @@ -1886,3 +1890,5 @@ def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): self.ma_exp_time = _ma_exp_time self.D_ma_time = _D_ma_time + + log SetNewMATime(_ma_exp_time, _D_ma_time) From 6de8afd41a7ca8ebdd06d1c13caf72986ecc813c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:51:40 +0100 Subject: [PATCH 252/337] improve for loop in non meta impl to use new bounds --- contracts/main/CurveStableSwapNG.vy | 55 ++++++----------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 69fbc66e..3136b838 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -298,10 +298,7 @@ def __init__( _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) _rate_oracles: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if i < N_COINS_128 - 1: self.last_prices_packed.append(self.pack_2(10**18, 10**18)) @@ -435,10 +432,7 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: """ rates: DynArray[uint256, MAX_COINS] = rate_multipliers - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if asset_types[i] == 1 and not rate_oracles[i] == 0: @@ -482,10 +476,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances_i: uint256 = 0 - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if 2 in asset_types: balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] @@ -590,10 +581,7 @@ def add_liquidity( # -------------------------- Do Transfers In ----------------------------- - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if _amounts[i] > 0: @@ -635,10 +623,7 @@ def add_liquidity( unsafe_mul(4, unsafe_sub(N_COINS, 1)) ) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): ideal_balance = D1 * old_balances[i] / D0 difference = 0 @@ -740,10 +725,7 @@ def remove_liquidity_imbalance( D0: uint256 = self.get_D_mem(rates, old_balances, amp) new_balances: DynArray[uint256, MAX_COINS] = old_balances - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if _amounts[i] != 0: new_balances[i] -= _amounts[i] @@ -763,10 +745,7 @@ def remove_liquidity_imbalance( difference: uint256 = 0 new_balance: uint256 = 0 - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): ideal_balance = D1 * old_balances[i] / D0 difference = 0 @@ -823,10 +802,7 @@ def remove_liquidity( balances: DynArray[uint256, MAX_COINS] = self._balances() value: uint256 = 0 - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): value = unsafe_div(balances[i] * _burn_amount, total_supply) assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" @@ -989,10 +965,7 @@ def _withdraw_admin_fees(): assert fee_receiver != empty(address) # dev: fee receiver not set admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): if admin_balances[i] > 0: @@ -1213,9 +1186,7 @@ def _xp_mem( ) -> DynArray[uint256, MAX_COINS]: result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - if i == N_COINS_128: - break + for i in range(N_COINS_128, bound=MAX_COINS_128): result.append(unsafe_div(_rates[i] * _balances[i], PRECISION)) return result @@ -1323,11 +1294,7 @@ def _get_p( ANN: uint256 = unsafe_mul(amp, N_COINS) Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - + for i in range(N_COINS_128, bound=MAX_COINS_128): Dr = Dr * D / xp[i] p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) From 12f1b7013ef2379fe2a0083be13cbfceb967448c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:50:58 +0100 Subject: [PATCH 253/337] fix incorrect division --- contracts/main/CurveStableSwapNG.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 3136b838..d3493d25 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1254,7 +1254,7 @@ def _calc_withdraw_one_coin( xavg = xp_j dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) - xp_reduced[j] = unsafe_div(xp_j - dynamic_fee * dx_expected, FEE_DENOMINATOR) + xp_reduced[j] = xp_j - unsafe_div(dynamic_fee * dx_expected, FEE_DENOMINATOR) dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees From 8abe95e80d23511feb1b9d5dbc55708b60f516d2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:05:26 +0100 Subject: [PATCH 254/337] fix unsafe div #2 --- contracts/main/CurveStableSwapNG.vy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index d3493d25..d44df5ce 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -944,7 +944,7 @@ def _exchange( # ------------------------------- Exchange ------------------------------- - x: uint256 = unsafe_div(xp[i] + dx * rates[i], PRECISION) + x: uint256 = xp[i] + unsafe_div(dx * rates[i], PRECISION) dy: uint256 = self.__exchange(x, xp, rates, i, j) assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" @@ -1080,8 +1080,9 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) D = ( - (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * - D / ( + (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * D + / + ( unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + unsafe_add(N_COINS, 1) * D_P ) From 7483832c4d999f275a842ac5336379fea79aa743 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:30:31 +0100 Subject: [PATCH 255/337] fix tests --- tests/fixtures/tokens.py | 2 +- tests/pools/test_liquidity.py | 31 +++++++++---------------------- tests/test_oracles.py | 16 ---------------- tests/utils/transactions.py | 2 +- 4 files changed, 11 insertions(+), 40 deletions(-) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index cdfcbd3a..2a98324b 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -41,7 +41,7 @@ def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): for i, d in enumerate(decimals): - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], i == 0)) + tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], True)) return tokens diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index f8793ffa..32b188d5 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -229,18 +229,6 @@ def test_initial( assert und_coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) assert und_coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) - # TODO: boa hangs with it, with added single print it passes - # @pytest.mark.parametrize("idx", (0, 1)) - # def test_initial_liquidity_missing_coin( - # self, alice, initial_setup_alice, swap, pool_type, decimals, meta_decimals, idx - # ): - # swap_decimals = decimals if pool_type == 0 else [meta_decimals, 18] - # amounts = [10**i for i in swap_decimals] - # amounts[idx] = 0 - # - # with boa.reverts(): - # swap.add_liquidity(amounts, 0, sender=alice) - @pytest.mark.usefixtures("initial_setup") class TestRemoveLiquidity: @pytest.mark.parametrize("min_amount", (0, 1)) @@ -326,7 +314,7 @@ def test_remove_one( amounts = [0] * pool_size amounts[idx] = deposit_amounts[idx] // 2 - lp_balance = pool_size * 1_000_000 * 10**18 + lp_balance = pool_size * deposit_amounts[idx] swap.remove_liquidity_imbalance(amounts, lp_balance, sender=alice) coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] @@ -342,7 +330,8 @@ def test_remove_one( ideal_balance = (2 * pool_size - 1) * lp_balance / (2 * pool_size) assert actual_balance == actual_total_supply - assert ideal_balance * 0.9994 < actual_balance < ideal_balance + assert ideal_balance * 0.9994 < actual_balance + assert actual_balance < ideal_balance * 1.07 @pytest.mark.parametrize("divisor", [1, 2, 10]) def test_exceed_max_burn(self, alice, swap, pool_size, divisor, deposit_amounts): @@ -420,15 +409,13 @@ def test_amount_exceeds_balance(self, bob, swap, idx): with boa.reverts(): swap.remove_liquidity_one_coin(1, idx, 0, sender=bob) - # TODO: this hangs - # def test_below_zero(self, alice, swap): - # with boa.reverts(): - # swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) + def test_below_zero(self, alice, swap): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) - # TODO: this hangs - # def test_above_n_coins(self, alice, swap, pool_size): - # with boa.reverts(): - # swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) + def test_above_n_coins(self, alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) @pytest.mark.parametrize("idx", range(2)) def test_event(self, alice, bob, swap, idx, pool_type): diff --git a/tests/test_oracles.py b/tests/test_oracles.py index 8a4dc726..bdc3527b 100644 --- a/tests/test_oracles.py +++ b/tests/test_oracles.py @@ -28,10 +28,6 @@ def get_D(swap, math): @settings(**SETTINGS) def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): - for token in pool_tokens: - if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: - return # TODO: rebasing tokens that rebase downwards are causing trouble here. - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: @@ -70,10 +66,6 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): @settings(**SETTINGS) def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): - for token in pool_tokens: - if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: - return # TODO: rebasing tokens that rebase downwards are causing trouble here. - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: @@ -117,10 +109,6 @@ def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, @pytest.mark.only_for_pool_type(0) def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount): - for token in pool_tokens: - if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: - return - p_oracle_before = swap.price_oracle(0) print("before", p_oracle_before) @@ -155,10 +143,6 @@ def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amo @settings(**SETTINGS) def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt, math_implementation): - for token in pool_tokens: - if "IS_UP" in token._immutables.__dict__.keys() and not token._immutables.IS_UP: - return # TODO: rebasing tokens that rebase downwards are causing trouble here. - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: diff --git a/tests/utils/transactions.py b/tests/utils/transactions.py index 97d44e72..55f4b603 100644 --- a/tests/utils/transactions.py +++ b/tests/utils/transactions.py @@ -8,7 +8,7 @@ def call_returning_result_and_logs( contract: VyperContract, function_name: str, *args, value=0, gas=None, sender=None, **kwargs ) -> tuple[Any, list[Event]]: func: VyperFunction = getattr(contract, function_name) - calldata_bytes = func._prepare_calldata(*args, **kwargs) + calldata_bytes = func.prepare_calldata(*args, **kwargs) override_bytecode = getattr(func, "override_bytecode", None) with func.contract._anchor_source_map(func._source_map): From bb8e9db6b49ac791ade88ce3cbc1e1c2737b997d Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:20:48 +0100 Subject: [PATCH 256/337] fix unsafe div --- contracts/main/CurveStableSwapNG.vy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index d44df5ce..c576e20a 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -613,7 +613,7 @@ def add_liquidity( difference: uint256 = 0 new_balance: uint256 = 0 - ys: uint256 = unsafe_div((D0 + D1), N_COINS) + ys: uint256 = unsafe_div(D0 + D1, N_COINS) xs: uint256 = 0 _dynamic_fee_i: uint256 = 0 @@ -1306,7 +1306,7 @@ def _get_p( if i == N_COINS: break - p.append(10**18 * unsafe_div(xp0_A + Dr * xp[0], xp[i]) / (xp0_A + Dr)) + p.append(10**18 * (xp0_A + unsafe_div(Dr * xp[0], xp[i])) / (xp0_A + Dr)) return p From 1917d81bb448298713634f3fba506191b2e2ab3f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:42:20 +0100 Subject: [PATCH 257/337] change storage before external call; add reentrancy lock for withdraw admin fees --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 3 +- tests/pools/test_liquidity.py | 16 ++---- tests/{ => pools}/test_oracles.py | 72 +++++++++++++++++++------ 4 files changed, 63 insertions(+), 30 deletions(-) rename tests/{ => pools}/test_oracles.py (78%) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index ceca3ab2..6f64c514 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -483,10 +483,10 @@ def _transfer_out( """ if asset_type != 2: + self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True ) - self.stored_balances[_coin_idx] -= _amount else: diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index c576e20a..c2753a60 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -404,10 +404,10 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): if not 2 in asset_types: + self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True ) - self.stored_balances[_coin_idx] -= _amount else: @@ -847,6 +847,7 @@ def remove_liquidity( @external +@nonreentrant('lock') def withdraw_admin_fees(): """ @notice Claim admin fees. Callable by anyone. diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index 32b188d5..b26cb192 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -29,12 +29,8 @@ def test_add_liquidity( for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): if pool_token_types[i] == 2: is_ideal = False - if i == 0: # up rebasing - assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 - else: # down rebasing - assert pool_token.balanceOf(bob) <= initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] * 2 + assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 else: assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 @@ -86,12 +82,8 @@ def test_add_one_coin( for i, pool_token in enumerate(pool_tokens): if pool_token_types[i] == 2: is_ideal = False - if i == 0: # up rebasing - assert pool_token.balanceOf(bob) >= initial_amounts[i] - amounts[i] - 1 - assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] + amounts[i] - 1 - else: # down rebasing - assert pool_token.balanceOf(bob) <= initial_amounts[i] - amounts[i] + 1 - assert pool_token.balanceOf(swap.address) <= deposit_amounts[i] + amounts[i] + 1 + assert pool_token.balanceOf(bob) >= initial_amounts[i] - amounts[i] - 1 + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] + amounts[i] - 1 else: assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] assert pool_token.balanceOf(swap.address) == deposit_amounts[i] + amounts[i] diff --git a/tests/test_oracles.py b/tests/pools/test_oracles.py similarity index 78% rename from tests/test_oracles.py rename to tests/pools/test_oracles.py index bdc3527b..4d519752 100644 --- a/tests/test_oracles.py +++ b/tests/pools/test_oracles.py @@ -22,6 +22,31 @@ def get_D(swap, math): return math.get_D(xp, amp, swap.N_COINS()) +def check_oracle(swap, dt): + # amm prices: + p_amm = [] + for n in range(swap.N_COINS() - 1): + + _p = swap.get_p(n) + + assert approx(swap.last_price(n), _p, 1e-5) + assert approx(swap.price_oracle(n), 10**18, 1e-5) + + p_amm.append(_p) + + # time travel dt amount: + boa.env.time_travel(dt) + + # calculate weights based on time travelled: + w = exp(-dt / 866) + + # check: + for n in range(swap.N_COINS() - 1): + + p1 = int(10**18 * w + p_amm[n] * (1 - w)) + assert approx(swap.price_oracle(n), p1, 1e-5) + + @given( amount=strategy("uint256", min_value=1, max_value=10**6), ) @@ -64,7 +89,7 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): dt=strategy("uint256", min_value=0, max_value=10**6), ) @settings(**SETTINGS) -def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): +def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): i, j = random.sample(range(swap.N_COINS()), 2) @@ -77,29 +102,44 @@ def test_price_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, boa.env.time_travel(dt0) swap.exchange(i, j, amount, 0, sender=bob) + check_oracle(swap, dt) - # amm prices: - p_amm = [] - for n in range(swap.N_COINS() - 1): - _p = swap.get_p(n) +@given( + amount=strategy("uint256", min_value=1, max_value=10**5), + dt0=strategy("uint256", min_value=0, max_value=10**6), + dt=strategy("uint256", min_value=0, max_value=10**6), +) +@settings(**SETTINGS) +def test_price_ema_remove_one(swap, alice, amount, dt0, dt): - assert approx(swap.last_price(n), _p, 1e-5) - assert approx(swap.price_oracle(n), 10**18, 1e-5) + i = random.sample(range(swap.N_COINS()), 1)[0] + alice_lp_bal = swap.balanceOf(alice) + amt_to_remove = int(alice_lp_bal * amount / (10**5 - 1)) - p_amm.append(_p) + boa.env.time_travel(dt0) + swap.remove_liquidity_one_coin(amt_to_remove, i, 0, sender=alice) - # time travel dt amount: - boa.env.time_travel(dt) + check_oracle(swap, dt) - # calculate weights based on time travelled: - w = exp(-dt / 866) - # check: - for n in range(swap.N_COINS() - 1): +@given( + frac=strategy("uint256", min_value=1, max_value=8), + dt0=strategy("uint256", min_value=0, max_value=10**6), + dt=strategy("uint256", min_value=0, max_value=10**6), +) +@settings(**SETTINGS) +def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amounts, frac): - p1 = int(10**18 * w + p_amm[n] * (1 - w)) - assert approx(swap.price_oracle(n), p1, 1e-5) + i = random.sample(range(swap.N_COINS()), 1)[0] + amounts = [0] * pool_size + amounts[i] = deposit_amounts[i] // frac + lp_balance = pool_size * deposit_amounts[i] + + boa.env.time_travel(dt0) + swap.remove_liquidity_imbalance(amounts, lp_balance, sender=alice) + + check_oracle(swap, dt) @given( From e2efdc96d3932c366247f18882638dd3e5f744e5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:38:25 +0100 Subject: [PATCH 258/337] remove extra Transfer log in remove liquidity one --- contracts/main/CurveStableSwapMetaNG.vy | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 6f64c514..8704db6e 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -890,8 +890,6 @@ def remove_liquidity_one_coin( self._burnFrom(msg.sender, _burn_amount) - log Transfer(msg.sender, empty(address), _burn_amount) - self._transfer_out(i, dy, _receiver) log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) From 8c971995bcc3f2cbd8c4752a09ddca1f3e2c1ec5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:40:02 +0100 Subject: [PATCH 259/337] use more correct input in TokenExchange --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 8704db6e..4ca76e51 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1163,7 +1163,7 @@ def _exchange( # ------------------------------------------------------------------------ - log TokenExchange(msg.sender, i, _dx, j, dy) + log TokenExchange(msg.sender, i, dx, j, dy) return dy diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index c2753a60..295ea490 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -955,7 +955,7 @@ def _exchange( # ------------------------------------------------------------------------ - log TokenExchange(msg.sender, i, _dx, j, dy) + log TokenExchange(msg.sender, i, dx, j, dy) return dy From fc0dc6ec526f5085c5f9993519a5946860592383 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:49:58 +0100 Subject: [PATCH 260/337] check approve in meta constructor --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 4ca76e51..b2d20a32 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -339,7 +339,7 @@ def __init__( if i < BASE_N_COINS: # Approval needed for add_liquidity operation on base pool in # _exchange_underlying: - ERC20(_base_coins[i]).approve( + assert ERC20(_base_coins[i]).approve( BASE_POOL, max_value(uint256), default_return_value = True From e42c3ae34ccdb4811b85aa1978b077e53049d2b4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:52:26 +0100 Subject: [PATCH 261/337] emit event in transferFrom --- contracts/main/CurveStableSwapMetaNG.vy | 4 +++- contracts/main/CurveStableSwapNG.vy | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index b2d20a32..0cbe4b1f 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1537,7 +1537,9 @@ def transferFrom(_from : address, _to : address, _value : uint256) -> bool: _allowance: uint256 = self.allowance[_from][msg.sender] if _allowance != max_value(uint256): - self.allowance[_from][msg.sender] = _allowance - _value + _new_allowance: uint256 = _allowance - _value + self.allowance[_from][msg.sender] = _new_allowance + log Approval(_from, msg.sender, _new_allowance) return True diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 295ea490..8a96608c 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1570,7 +1570,9 @@ def transferFrom(_from : address, _to : address, _value : uint256) -> bool: _allowance: uint256 = self.allowance[_from][msg.sender] if _allowance != max_value(uint256): - self.allowance[_from][msg.sender] = _allowance - _value + _new_allowance: uint256 = _allowance - _value + self.allowance[_from][msg.sender] = _new_allowance + log Approval(_from, msg.sender, _new_allowance) return True From 33c539b80cfeaa579539846e3a16e262c71229b4 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 6 Dec 2023 19:48:08 +0100 Subject: [PATCH 262/337] restrict non-NG base pools in metapools --- contracts/main/CurveStableSwapMetaNG.vy | 5 + tests/conftest.py | 12 +- tests/pools/meta/test_exchange_underlying.py | 95 +++------- tests/pools/meta/test_meta.py | 172 ----------------- tests/pools/meta/test_meta_new_base.py | 190 ------------------- tests/pools/meta/test_revert_meta_ng_base.py | 132 +++++++++++++ tests/pools/test_donation_get_D.py | 3 +- tests/pools/test_erc4626_swaps.py | 3 +- tests/pools/test_exchange_received.py | 3 +- tests/pools/test_oracles.py | 4 - tests/utils/__init__.py | 7 + 11 files changed, 185 insertions(+), 441 deletions(-) delete mode 100644 tests/pools/meta/test_meta.py delete mode 100644 tests/pools/meta/test_meta_new_base.py create mode 100644 tests/pools/meta/test_revert_meta_ng_base.py diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 0cbe4b1f..69edb969 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -10,6 +10,8 @@ of another pool. This exposes methods such as exchange_underlying, which exchanges token 0 <> token b1, b2, .. bn, where b is base pool and bn is the nth coin index of the base pool. + CAUTION: Does not work if base pool is an NG pool. Use a different metapool + implementation index in the factory. Asset Types: 0. Standard ERC20 token with no additional features. Note: Users are advised to do careful due-diligence on @@ -326,6 +328,9 @@ def __init__( """ assert len(_base_coins) <= 3 # dev: implementation does not support base pool with more than 3 coins + # The following reverts if BASE_POOL is an NG implementaion. + assert not raw_call(_base_pool, method_id("D_ma_time()"), revert_on_failure=False) + math = Math(_math_implementation) BASE_POOL = _base_pool BASE_COINS = _base_coins diff --git a/tests/conftest.py b/tests/conftest.py index 6b44bbb9..531da375 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,8 @@ import boa import pytest +from tests.utils import get_asset_types_in_pool + pytest_plugins = [ "tests.fixtures.accounts", "tests.fixtures.constants", @@ -172,7 +174,7 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals): def skip_by_token_type(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: - asset_types = [tkn.asset_types() for tkn in pool_tokens] + asset_types = [tkn.asset_type() for tkn in pool_tokens] if not any(asset_type in only_for_token_types.args for asset_type in asset_types): pytest.skip("skipped because no tokens for these types") @@ -181,7 +183,7 @@ def skip_by_token_type(request, pool_tokens): def skip_rebasing(request, swap): only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") if only_for_token_types: - if 2 in swap._immutables.asset_types: + if 2 in get_asset_types_in_pool(swap): pytest.skip("skipped because test includes rebasing tokens") @@ -189,7 +191,7 @@ def skip_rebasing(request, swap): def skip_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") if only_for_token_types: - asset_types = [tkn.asset_types() for tkn in pool_tokens] + asset_types = [tkn.asset_type() for tkn in pool_tokens] asset_types_contains_oracle = 1 in asset_types if asset_types_contains_oracle: pytest.skip("skipped because test includes oraclised tokens") @@ -199,7 +201,7 @@ def skip_oracle(request, pool_tokens): def only_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") if only_for_token_types: - asset_types = [tkn.asset_types() for tkn in pool_tokens] + asset_types = [tkn.asset_type() for tkn in pool_tokens] asset_types_contains_rebasing = 1 in asset_types if not asset_types_contains_rebasing: pytest.skip("skipped because test excludes oraclised tokens") @@ -209,7 +211,7 @@ def only_oracle(request, pool_tokens): def only_rebasing(request, swap): marker = request.node.get_closest_marker("contains_rebasing_tokens") if marker: - asset_types_contains_rebasing = 2 in swap._immutables.asset_types + asset_types_contains_rebasing = 2 in get_asset_types_in_pool(swap) if not asset_types_contains_rebasing: pytest.skip("skipped because test excludes rebasing tokens") diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index bdc58686..64c84f19 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -1,80 +1,41 @@ import itertools import pytest -from pytest import approx + +from tests.utils import approx pytestmark = pytest.mark.usefixtures("initial_setup") @pytest.mark.only_for_pool_type(1) # only for metapools -class TestMetaExchangeUnderlying: - @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) - @pytest.mark.only_oracle_tokens - def test_amounts(self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): - - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount_sent = 10 ** underlying_decimals[sending] - - if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: - underlying_tokens[sending]._mint_for_testing(bob, amount_sent) - - initial_swap_balances = [swap.balances(0), swap.balances(1)] # noqa: F841 - expected_received = swap.get_dy_underlying(sending, receiving, amount_sent) - expected_ratio_received_sent = expected_received / amount_sent # noqa: F841 - - initial_amount_sending = underlying_tokens[sending].balanceOf(bob) - initial_amount_receiving = underlying_tokens[receiving].balanceOf(bob) +@pytest.mark.skip_oracle_tokens +@pytest.mark.skip_rebasing_tokens +@pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) +def test_amounts(bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): - received_true = swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 - swap_logs = swap.get_logs() # noqa: F841 - assert underlying_tokens[sending].balanceOf(bob) == initial_amount_sending - amount_sent + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount_sent = 10 ** underlying_decimals[sending] - amount_received = underlying_tokens[receiving].balanceOf(bob) - initial_amount_receiving - swap_balances = [swap.balances(0), swap.balances(1)] # noqa: F841 + if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: + underlying_tokens[sending]._mint_for_testing(bob, amount_sent) - assert 0.999 <= amount_received / amount_sent < 1 + expected_received = swap.get_dy_underlying(sending, receiving, amount_sent) + received_true = swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 + assert approx(received_true, expected_received, 1e-3) - @pytest.mark.skip_rebasing_tokens - @pytest.mark.skip_oracle_tokens - @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) - def test_fees( - self, - bob, - swap, - underlying_tokens, - sending, - receiving, - meta_decimals, - base_pool, - base_pool_decimals, - ): - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10000 * 10 ** underlying_decimals[sending] - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) - admin_idx = min(1, receiving) - admin_fee = swap.admin_balances(admin_idx) - - expected = 2 * 10 ** underlying_decimals[admin_idx] - if admin_idx == 1: - expected = expected * 10**18 // base_pool.get_virtual_price() - assert expected / admin_fee == approx(1, rel=1e-3) - - @pytest.mark.skip_rebasing_tokens - @pytest.mark.skip_oracle_tokens - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) - def test_min_dy_underlying( - self, bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals - ): - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10 ** underlying_decimals[sending] - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - - expected = swap.get_dy_underlying(sending, receiving, amount) - received = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) - - assert abs(expected - received) / received < 0.00001 +@pytest.mark.only_for_pool_type(1) # only for metapools +@pytest.mark.skip_rebasing_tokens +@pytest.mark.skip_oracle_tokens +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +def test_min_dy_underlying(bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + + expected = swap.get_dy_underlying(sending, receiving, amount) + received = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + + assert approx(expected, received, 1e-3) diff --git a/tests/pools/meta/test_meta.py b/tests/pools/meta/test_meta.py deleted file mode 100644 index 4e818da5..00000000 --- a/tests/pools/meta/test_meta.py +++ /dev/null @@ -1,172 +0,0 @@ -import boa -import pytest - -from tests.utils.tokens import mint_for_testing - - -@pytest.fixture(scope="module") -def base_pool_decimals(): - return [18, 18, 18] - - -@pytest.fixture(scope="module") -def base_pool_tokens(deployer, base_pool_decimals): - tokens = [] - with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", base_pool_decimals[2])) - - return tokens - - -@pytest.fixture(scope="module") -def meta_token(deployer): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20.vy", - "OTA", - "OTA", - 18, - ) - - -@pytest.fixture(scope="module") -def asset_types(): - _asset_types = [0, 0, 0] - return _asset_types - - -@pytest.fixture(scope="module") -def base_pool( - deployer, - factory, - base_pool_tokens, - zero_address, - amm_interface, - asset_types, - set_pool_implementations, -): - pool_size = len(base_pool_tokens) - offpeg_fee_multiplier = 20000000000 - method_ids = [bytes(b"")] * pool_size - oracles = [zero_address] * pool_size - A = 1000 - fee = 3000000 - - with boa.env.prank(deployer): - pool = factory.deploy_plain_pool( - "test", - "test", - [t.address for t in base_pool_tokens], - A, - fee, - offpeg_fee_multiplier, - 866, - 0, - asset_types, - method_ids, - oracles, - ) - - return amm_interface.at(pool) - - -@pytest.fixture(scope="module") -def metapool_tokens(meta_token, base_pool): - return [meta_token, base_pool] - - -@pytest.fixture(scope="module") -def add_base_pool( - owner, - factory, - base_pool, - base_pool_tokens, -): - with boa.env.prank(owner): - factory.add_base_pool( - base_pool.address, - base_pool.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), - ) - - -@pytest.fixture(scope="module") -def empty_swap( - deployer, - factory, - zero_address, - meta_token, - base_pool, - amm_interface_meta, - asset_types, - add_base_pool, - set_metapool_implementations, -): - method_id = bytes(b"") - oracle = zero_address - offpeg_fee_multiplier = 20000000000 - asset_type = meta_token.asset_type() - A = 1000 - fee = 3000000 - - with boa.env.prank(deployer): - pool = factory.deploy_metapool( - base_pool.address, # _base_pool: address - "test", # _name: String[32], - "test", # _symbol: String[10], - meta_token.address, # _coin: address, - A, # _A: uint256, - fee, # _fee: uint256, - offpeg_fee_multiplier, - 866, # _ma_exp_time: uint256, - 0, # _implementation_idx: uint256 - asset_type, # _asset_type: uint8 - method_id, # _method_id: bytes4 - oracle, # _oracle: address - ) - - return amm_interface_meta.at(pool) - - -@pytest.fixture(scope="module") -def deposit_amounts(meta_token, bob, base_pool, base_pool_tokens, base_pool_decimals, empty_swap): - _deposit_amounts = [] - INITIAL_AMOUNT = 3_000_000 - - _deposit_amount = INITIAL_AMOUNT // 3 * 10 ** meta_token.decimals() - if meta_token.balanceOf(bob) < _deposit_amount: - mint_for_testing(bob, _deposit_amount, meta_token, False) - _deposit_amounts.append(_deposit_amount) - - def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = INITIAL_AMOUNT // 3 - with boa.env.prank(user): - for d, token in zip(base_pool_decimals, base_pool_tokens): - token._mint_for_testing(user, amount * 10**d) - token.approve(base_pool.address, 2**256 - 1) - - amounts = [amount * 10**d for d in base_pool_decimals] - base_pool.add_liquidity(amounts, 0) - - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - _deposit_amounts.append(INITIAL_AMOUNT // 3 * 10 ** base_pool.decimals()) - - return _deposit_amounts - - -@pytest.fixture(scope="module") -def swap(empty_swap, bob, deposit_amounts, metapool_tokens): - for token in metapool_tokens: - token.approve(empty_swap.address, 2**256 - 1, sender=bob) - empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) - return empty_swap - - -def test_exchange(swap, charlie, meta_token): - amount = 1000 * 10**18 - mint_for_testing(charlie, amount, meta_token, False) - meta_token.approve(swap.address, 2**256 - 1, sender=charlie) - swap.exchange(0, 1, amount, 0, sender=charlie) diff --git a/tests/pools/meta/test_meta_new_base.py b/tests/pools/meta/test_meta_new_base.py deleted file mode 100644 index 944ec63e..00000000 --- a/tests/pools/meta/test_meta_new_base.py +++ /dev/null @@ -1,190 +0,0 @@ -import boa -import pytest - -from tests.utils.tokens import mint_for_testing - - -@pytest.fixture(scope="module") -def base_pool_decimals(): - return [18, 18] - - -@pytest.fixture(scope="module") -def base_pool_tokens(deployer, base_pool_decimals): - tokens = [] - with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) - - return tokens - - -@pytest.fixture(scope="module") -def meta_token(deployer): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20.vy", - "OTA", - "OTA", - 18, - ) - - -@pytest.fixture(scope="module") -def asset_types(): - _asset_types = [0, 0] - return _asset_types - - -@pytest.fixture(scope="module") -def base_pool( - deployer, - factory, - base_pool_tokens, - zero_address, - amm_interface, - asset_types, - set_pool_implementations, -): - pool_size = len(base_pool_tokens) - offpeg_fee_multiplier = 20000000000 - method_ids = [bytes(b"")] * pool_size - oracles = [zero_address] * pool_size - A = 1000 - fee = 3000000 - - with boa.env.prank(deployer): - pool = factory.deploy_plain_pool( - "test", - "test", - [t.address for t in base_pool_tokens], - A, - fee, - offpeg_fee_multiplier, - 866, - 0, - asset_types, - method_ids, - oracles, - ) - - return amm_interface.at(pool) - - -@pytest.fixture(scope="module") -def metapool_tokens(meta_token, base_pool): - return [meta_token, base_pool] - - -@pytest.fixture(scope="module") -def add_base_pool( - owner, - factory, - base_pool, - base_pool_tokens, -): - with boa.env.prank(owner): - factory.add_base_pool( - base_pool.address, - base_pool.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), - ) - - -@pytest.fixture(scope="module") -def empty_swap( - deployer, - factory, - zero_address, - meta_token, - base_pool, - amm_interface_meta, - asset_types, - add_base_pool, - set_metapool_implementations, -): - method_id = bytes(b"") - oracle = zero_address - offpeg_fee_multiplier = 20000000000 - asset_type = meta_token.asset_type() - A = 1000 - fee = 3000000 - - with boa.env.prank(deployer): - pool = factory.deploy_metapool( - base_pool.address, # _base_pool: address - "test", # _name: String[32], - "test", # _symbol: String[10], - meta_token.address, # _coin: address, - A, # _A: uint256, - fee, # _fee: uint256, - offpeg_fee_multiplier, - 866, # _ma_exp_time: uint256, - 0, # _implementation_idx: uint256 - asset_type, # _asset_type: uint8 - method_id, # _method_id: bytes4 - oracle, # _oracle: address - ) - - return amm_interface_meta.at(pool) - - -@pytest.fixture(scope="module") -def deposit_amounts(meta_token, bob, base_pool, base_pool_tokens, base_pool_decimals, empty_swap): - _deposit_amounts = [] - INITIAL_AMOUNT = 3_000_000 - - _deposit_amount = INITIAL_AMOUNT // 3 * 10 ** meta_token.decimals() - if meta_token.balanceOf(bob) < _deposit_amount: - mint_for_testing(bob, _deposit_amount, meta_token, False) - _deposit_amounts.append(_deposit_amount) - - def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = INITIAL_AMOUNT // 3 - with boa.env.prank(user): - for d, token in zip(base_pool_decimals, base_pool_tokens): - token._mint_for_testing(user, amount * 10**d) - token.approve(base_pool.address, 2**256 - 1) - - amounts = [amount * 10**d for d in base_pool_decimals] - base_pool.add_liquidity(amounts, 0) - - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - _deposit_amounts.append(INITIAL_AMOUNT // 3 * 10 ** base_pool.decimals()) - - return _deposit_amounts - - -@pytest.fixture(scope="module") -def swap(empty_swap, bob, deposit_amounts, metapool_tokens): - for token in metapool_tokens: - token.approve(empty_swap.address, 2**256 - 1, sender=bob) - empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) - return empty_swap - - -@pytest.mark.only_for_pool_type(1) -def test_donation_exchange(swap, bob, base_pool, base_pool_tokens): - amount_rewards = 19 * 10**6 * 10**18 - - i = 1 - j = 0 - if amount_rewards > base_pool_tokens[i].balanceOf(bob): - mint_for_testing(bob, amount_rewards, base_pool_tokens[i], False) - - donation_amount = 10**6 * 10**18 - imbalance_liq_removed = 500_000 * 10**18 - - D = base_pool.get_virtual_price() - base_pool_tokens[i].transfer(base_pool, donation_amount, sender=bob) - assert base_pool.get_virtual_price() == D # donation does not change vprice - - bob_meta_lp_balance = swap.balanceOf(bob) - swap.remove_liquidity_imbalance([imbalance_liq_removed, 0], bob_meta_lp_balance, sender=bob) - bob_meta_lp_balance_after = swap.balanceOf(bob) - - received = base_pool.exchange_received(i, j, donation_amount, 0, bob, sender=bob) - - profit = imbalance_liq_removed + (received - donation_amount) + (bob_meta_lp_balance_after - bob_meta_lp_balance) - assert profit < 0 diff --git a/tests/pools/meta/test_revert_meta_ng_base.py b/tests/pools/meta/test_revert_meta_ng_base.py new file mode 100644 index 00000000..82f2d8df --- /dev/null +++ b/tests/pools/meta/test_revert_meta_ng_base.py @@ -0,0 +1,132 @@ +import boa +import pytest + + +@pytest.fixture(scope="module") +def borky_factory( + deployer, + fee_receiver, + owner, + gauge_implementation, + views_implementation, + math_implementation, + amm_implementation, + amm_implementation_meta, +): + with boa.env.prank(deployer): + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + owner, + ) + + with boa.env.prank(owner): + _factory.set_gauge_implementation(gauge_implementation.address) + _factory.set_views_implementation(views_implementation.address) + _factory.set_math_implementation(math_implementation.address) + _factory.set_pool_implementations(0, amm_implementation.address) + _factory.set_metapool_implementations(0, amm_implementation_meta.address) + + return _factory + + +# <--------------------- Functions ---------------------> + + +@pytest.fixture(scope="module") +def ng_base_pool_decimals(): + return [18, 18] + + +@pytest.fixture(scope="module") +def ng_base_pool_tokens(deployer, ng_base_pool_decimals): + tokens = [] + with boa.env.prank(deployer): + tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", ng_base_pool_decimals[0])) + tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", ng_base_pool_decimals[1])) + + return tokens + + +@pytest.fixture(scope="module") +def ng_base_pool( + deployer, + borky_factory, + ng_base_pool_tokens, + zero_address, + amm_interface, +): + asset_types = [0, 0] + pool_size = len(ng_base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = borky_factory.deploy_plain_pool( + "test", + "test", + [t.address for t in ng_base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + asset_types, + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def add_ng_base_pool( + owner, + borky_factory, + ng_base_pool, + ng_base_pool_tokens, +): + with boa.env.prank(owner): + borky_factory.add_base_pool( + ng_base_pool.address, + ng_base_pool.address, + [0] * len(ng_base_pool_tokens), + len(ng_base_pool_tokens), + ) + + +def test_revert_metapool_deployment_with_ng_base_pool( + deployer, borky_factory, zero_address, add_ng_base_pool, ng_base_pool +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + A = 1000 + fee = 3000000 + meta_token = boa.load( + "contracts/mocks/ERC20.vy", + "OTA", + "OTA", + 18, + ) + asset_type = meta_token.asset_type() + + with boa.reverts(): + borky_factory.deploy_metapool( + ng_base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + sender=deployer, + ) diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index af2aa3dd..1e700dde 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -1,6 +1,7 @@ import boa import pytest +from tests.utils import get_asset_types_in_pool from tests.utils.tokens import mint_for_testing @@ -25,7 +26,7 @@ def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): # donate 1 wei and attempt adding liquidity: pool_tokens[0].transfer(swap, 10, sender=bob) - if 2 in swap._immutables.asset_types: + if 2 in get_asset_types_in_pool(swap): with boa.reverts(): # division by zero error expected for rebasing implementation swap.add_liquidity(amounts, 0, sender=bob) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 0ae7af37..47467fe5 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -4,6 +4,7 @@ import pytest from eth_utils import function_signature_to_4byte_selector +from tests.utils import get_asset_types_in_pool from tests.utils.tokens import mint_for_testing @@ -191,7 +192,7 @@ def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): if "RebasingConditional" in pool_tokens[i].filename: pool_tokens[i].rebase() - assert 2 in swap._immutables.asset_types + assert 2 in get_asset_types_in_pool(swap) calculated = swap.get_dy(i, j, amount_in) dy = swap.exchange(i, j, amount_in, int(0.99 * calculated), sender=charlie) diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 1e62c44d..26f831a2 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -2,6 +2,7 @@ import pytest from tests.fixtures.constants import INITIAL_AMOUNT +from tests.utils import get_asset_types_in_pool from tests.utils.tokens import mint_for_testing SWAP_AMOUNT = INITIAL_AMOUNT // 1000 @@ -143,6 +144,6 @@ def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, rece @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - if 2 in swap._immutables.asset_types: + if 2 in get_asset_types_in_pool(swap): with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/pools/test_oracles.py b/tests/pools/test_oracles.py index 4d519752..9b732bb4 100644 --- a/tests/pools/test_oracles.py +++ b/tests/pools/test_oracles.py @@ -149,9 +149,6 @@ def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amo @pytest.mark.only_for_pool_type(0) def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount): - p_oracle_before = swap.price_oracle(0) - print("before", p_oracle_before) - # calc amount in: amount_in = amount * 10 ** (decimals[0]) @@ -170,7 +167,6 @@ def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amo # check if price oracle is way too high p_oracle_after = swap.price_oracle(0) - print("after", p_oracle_after) assert p_oracle_after < 2 * 10**18 diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index b274793d..db30f6eb 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -14,3 +14,10 @@ def approx(x1: int, x2: int, precision: int, abs_precision=None): elif x1 == 0: return abs(x2) <= abs_precision return result or (abs(log(x1 / x2)) <= precision) + + +def get_asset_types_in_pool(pool): + + if "asset_type" in pool._immutables.__dict__.keys(): + return [pool._immutables.asset_type] + return pool._immutables.asset_types From 7ac164bb7d9cf800d50adf549f10539684db69b8 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:53:00 +0100 Subject: [PATCH 263/337] edge cases in D calc; remove deployed addresses which need to be replaced. --- contracts/main/CurveStableSwapMetaNG.vy | 21 +++++--- contracts/main/CurveStableSwapNG.vy | 30 ++++++----- contracts/main/CurveStableSwapNGMath.vy | 9 ++-- contracts/main/CurveStableSwapNGViews.vy | 9 ++-- contracts/main/LiquidityGauge.vy | 2 +- pyproject.toml | 1 + scripts/deploy_infra.py | 66 ++++++++++++------------ 7 files changed, 73 insertions(+), 65 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 69edb969..32856b0b 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -519,15 +519,14 @@ def _stored_rates() -> uint256[N_COINS]: if asset_type == 1 and not rate_oracle == 0: # NOTE: fetched_rate is assumed to be 10**18 precision - fetched_rate: uint256 = convert( - raw_call( - convert(rate_oracle % 2**160, address), - _abi_encode(rate_oracle & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ), - uint256 + oracle_response: Bytes[32] = raw_call( + convert(rate_oracle % 2**160, address), + _abi_encode(rate_oracle & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, ) + assert len(oracle_response) == 32 + fetched_rate: uint256 = convert(oracle_response, uint256) # rates[0] * fetched_rate / PRECISION rates[0] = unsafe_div(rates[0] * fetched_rate, PRECISION) @@ -840,6 +839,12 @@ def add_liquidity( # (re)instantiate D oracle if totalSupply is zero. self.last_D_packed = self.pack_2(D1, D1) + # Update D ma time: + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + if ma_last_time_unpacked[1] < block.timestamp: + ma_last_time_unpacked[1] = block.timestamp + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + assert mint_amount >= _min_mint_amount, "Slippage screwed you" # Mint pool tokens diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 8a96608c..02a1aa5f 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -437,15 +437,14 @@ def _stored_rates() -> DynArray[uint256, MAX_COINS]: if asset_types[i] == 1 and not rate_oracles[i] == 0: # NOTE: fetched_rate is assumed to be 10**18 precision - fetched_rate: uint256 = convert( - raw_call( - convert(rate_oracles[i] % 2**160, address), - _abi_encode(rate_oracles[i] & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ), - uint256 + oracle_response: Bytes[32] = raw_call( + convert(rate_oracles[i] % 2**160, address), + _abi_encode(rate_oracles[i] & ORACLE_BIT_MASK), + max_outsize=32, + is_static_call=True, ) + assert len(oracle_response) == 32 + fetched_rate: uint256 = convert(oracle_response, uint256) rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) @@ -653,6 +652,12 @@ def add_liquidity( # (re)instantiate D oracle if totalSupply is zero. self.last_D_packed = self.pack_2(D1, D1) + # Update D ma time: + ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) + if ma_last_time_unpacked[1] < block.timestamp: + ma_last_time_unpacked[1] = block.timestamp + self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) + assert mint_amount >= _min_mint_amount, "Slippage screwed you" # Mint pool tokens @@ -1069,15 +1074,14 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: D: uint256 = S Ann: uint256 = _amp * N_COINS - D_P: uint256 = 0 - Dprev: uint256 = 0 for i in range(255): - D_P = D + D_P: uint256 = D for x in _xp: - D_P = D_P * D / (x * N_COINS) - Dprev = D + D_P *= D / x + D_P /= pow_mod256(N_COINS, N_COINS) + Dprev: uint256 = D # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) D = ( diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index bc2bd55d..285a8d81 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -109,15 +109,14 @@ def get_D( D: uint256 = S Ann: uint256 = _amp * _n_coins - D_P: uint256 = 0 - Dprev: uint256 = 0 for i in range(255): - D_P = D + D_P: uint256 = D for x in _xp: - D_P = D_P * D / (x * _n_coins) # If division by 0, this will be borked: only withdrawal will work. And that is good - Dprev = D + D_P = D_P * D / x # If division by 0, this will be borked: only withdrawal will work. And that is good + D_P /= pow_mod256(_n_coins, _n_coins) + Dprev: uint256 = D # (Ann * S / A_PRECISION + D_P * _n_coins) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (_n_coins + 1) * D_P) D = ( diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 26b40eed..183355dc 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -575,15 +575,14 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256, N_COINS: uint256) -> D: uint256 = S Ann: uint256 = _amp * N_COINS - D_P: uint256 = 0 - Dprev: uint256 = 0 for i in range(255): - D_P = D + D_P: uint256 = D for x in _xp: - D_P = D_P * D / (x * N_COINS) - Dprev = D + D_P = D_P * D / x + D_P /= pow_mod256(N_COINS, N_COINS) + Dprev: uint256 = D D = (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) # Equality with the precision of 1 diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 6afa0965..8228ecec 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -573,7 +573,7 @@ def permit( assert ecrecover(digest, _v, _r, _s) == _owner # dev: invalid signature self.allowance[_owner][_spender] = _value - self.nonces[_owner] = nonce + 1 + self.nonces[_owner] = unsafe_add(nonce, 1) log Approval(_owner, _spender, _value) return True diff --git a/pyproject.toml b/pyproject.toml index b6144268..4c9131f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,4 +65,5 @@ known_first_party = "poetry" markers = [ "only_for_pool_type: running tests only for specific pool types", "only_for_token_types: running tests only if tokens of specific types are in pool", + "no_auto_generate: dont generate tests for this one", ] diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 57645a6f..b98b77c3 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -13,53 +13,53 @@ deployments = { # Ethereum "ethereum:sepolia": { - "math": "0xbc7654d2dd901aaaa3be4cb5bc0f10dea9f96443", - "views": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", "gauge": "", }, "ethereum:mainnet": { - "math": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", - "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "gauge": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", + "gauge": "", }, # Layer 2 "arbitrum:mainnet": { - "math": "0x3d6cB2F6DcF47CDd9C13E4e3beAe9af041d8796a", - "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { - "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, "linea:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -72,65 +72,65 @@ "factory": "", }, "pzkevm:mainnet": { - "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "views": "0x87fe17697d0f14a222e8bef386a0860ecffdd617", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { - "math": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "polygon:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "avax:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "ftm:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "bsc:mainnet": { - "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "views": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "celo:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { - "math": "0xf3A431008396df8A8b2DF492C913706BDB0874ef", - "views": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "math": "", + "views": "", "plain_amm": "", "meta_amm": "", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", From 72e34f65d2e6bfe6d4657156ee30c708f2036495 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:56:12 +0100 Subject: [PATCH 264/337] emit correct value in event --- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 32856b0b..bbc675d8 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -986,7 +986,7 @@ def remove_liquidity_imbalance( [_amounts[0], _amounts[1]], [fees[0], fees[1]], D1, - total_supply + total_supply - burn_amount ) return burn_amount diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 02a1aa5f..4ef1fe1a 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -776,10 +776,15 @@ def remove_liquidity_imbalance( assert burn_amount > 1 # dev: zero tokens burned assert burn_amount <= _max_burn_amount, "Slippage screwed you" - total_supply -= burn_amount self._burnFrom(msg.sender, burn_amount) - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) + log RemoveLiquidityImbalance( + msg.sender, + _amounts, + fees, + D1, + total_supply - burn_amount + ) return burn_amount From d2f21f820be9ac2e035927ad8bc6ef9f2dd368bc Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:08:39 +0100 Subject: [PATCH 265/337] do nothing if fee receiver is empty --- contracts/main/CurveStableSwapMetaNG.vy | 3 ++- contracts/main/CurveStableSwapNG.vy | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index bbc675d8..a9ab2300 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1203,7 +1203,8 @@ def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: def _withdraw_admin_fees(): fee_receiver: address = factory.fee_receiver() - assert fee_receiver != empty(address) # dev: fee receiver not set + if fee_receiver == empty(address): + return # Do nothing. admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128): diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 4ef1fe1a..2f6285fa 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -973,7 +973,8 @@ def _exchange( @internal def _withdraw_admin_fees(): fee_receiver: address = factory.fee_receiver() - assert fee_receiver != empty(address) # dev: fee receiver not set + if fee_receiver == empty(address): + return # Do nothing. admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128, bound=MAX_COINS_128): From cdbf9ede33e16869c711f1cb4d3ddb2bdb2dd5b1 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:17:06 +0100 Subject: [PATCH 266/337] some math updates and some gas improvements in meta --- contracts/main/CurveStableSwapMetaNG.vy | 15 +-------------- contracts/main/CurveStableSwapNG.vy | 2 +- contracts/main/CurveStableSwapNGMath.vy | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index a9ab2300..c68e3e69 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -694,20 +694,7 @@ def exchange_underlying( if i == 0 or j == 0: # meta swap - if i == 0: - - # xp[i] + dx_w_fee * rates[i] / PRECISION - x = xp[i] + unsafe_div(dx_w_fee * rates[i], PRECISION) - - else: - - # dx_w_fee is the number of base_pool LP tokens minted after - # base_pool.add_liquidity in self._transfer_in: - - # dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX] / PRECISION - x = unsafe_div(dx_w_fee * rates[MAX_METAPOOL_COIN_INDEX], PRECISION) - x += xp[MAX_METAPOOL_COIN_INDEX] - + x = xp[meta_i] + unsafe_div(dx_w_fee * rates[meta_i], PRECISION) dy = self.__exchange(x, xp, rates, meta_i, meta_j) # Adjust stored balances of meta-level tokens: diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 2f6285fa..22e6a25d 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1469,7 +1469,7 @@ def exp(x: int256) -> uint256: # If the result is `< 0.5`, we return zero. This happens when we have the following: # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". - if (x <= -42139678854452767551): + if (x <= -41446531673892822313): return empty(uint256) # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 285a8d81..25acc73e 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -218,7 +218,7 @@ def exp(x: int256) -> uint256: # If the result is `< 0.5`, we return zero. This happens when we have the following: # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". - if (x <= -42139678854452767551): + if (x <= -41446531673892822313): return empty(uint256) # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. From dcd4471c2b506b11eba48fab4838cc962f6ceddb Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:18:40 +0100 Subject: [PATCH 267/337] remove unnecessary storage read when not necessary --- contracts/main/CurveStableSwapMetaNG.vy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c68e3e69..2ded6898 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -427,7 +427,6 @@ def _transfer_in( @return amount of coins received """ _input_coin: ERC20 = ERC20(coins[coin_metapool_idx]) - _stored_balance: uint256 = self.stored_balances[coin_metapool_idx] _input_coin_is_in_base_pool: bool = False # Check if _transfer_in is being called by _exchange_underlying: @@ -443,7 +442,7 @@ def _transfer_in( if expect_optimistic_transfer: if not _input_coin_is_in_base_pool: - _dx = _dx - _stored_balance + _dx = _dx - self.stored_balances[coin_metapool_idx] assert _dx >= dx # dev: pool did not receive tokens for swap else: From 27864981c91698219af98f7d7ed91129f44c9e71 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:23:15 +0100 Subject: [PATCH 268/337] check _min_amounts length --- contracts/main/CurveStableSwapNG.vy | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 22e6a25d..07593e16 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -807,6 +807,7 @@ def remove_liquidity( """ total_supply: uint256 = self.total_supply assert _burn_amount > 0 # dev: invalid burn amount + assert len(_min_amounts) == N_COINS # dev: invalid array length for _min_amounts amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = self._balances() From 75c7c93a086d842ee99d93e86d6d4082860a3f10 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:28:14 +0100 Subject: [PATCH 269/337] do not send tokens to zero address --- contracts/main/CurveStableSwapMetaNG.vy | 7 +++++++ contracts/main/CurveStableSwapNG.vy | 3 +++ 2 files changed, 10 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 2ded6898..d8f067a1 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -485,6 +485,8 @@ def _transfer_out( @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ + assert receiver != empty(address) # dev: do not send tokens to zero_address + if asset_type != 2: self.stored_balances[_coin_idx] -= _amount @@ -649,6 +651,8 @@ def exchange_underlying( @param _receiver Address that receives `j` @return Actual amount of `j` received """ + assert _receiver != empty(address) # dev: do not send tokens to zero_address + rates: uint256[N_COINS] = self._stored_rates() old_balances: uint256[N_COINS] = self._balances() xp: uint256[N_COINS] = self._xp_mem(rates, old_balances) @@ -738,6 +742,8 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ + assert _receiver != empty(address) # dev: do not send LP tokens to zero_address + amp: uint256 = self._A() old_balances: uint256[N_COINS] = self._balances() rates: uint256[N_COINS] = self._stored_rates() @@ -872,6 +878,7 @@ def remove_liquidity_one_coin( @return Amount of coin received """ assert _burn_amount > 0 # dev: do not remove 0 LP tokens + dy: uint256 = 0 fee: uint256 = 0 xp: uint256[N_COINS] = empty(uint256[N_COINS]) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 07593e16..91ca9bd0 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -401,6 +401,7 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): @param _amount Amount of token to transfer out @param receiver Address to send the tokens to """ + assert receiver != empty(address) # dev: do not send tokens to zero_address if not 2 in asset_types: @@ -568,6 +569,8 @@ def add_liquidity( @param _receiver Address that owns the minted LP tokens @return Amount of LP tokens received by depositing """ + assert _receiver != empty(address) # dev: do not send LP tokens to zero_address + amp: uint256 = self._A() old_balances: DynArray[uint256, MAX_COINS] = self._balances() rates: DynArray[uint256, MAX_COINS] = self._stored_rates() From 1e8dc1a9a9ccf1636f8ec66987cfb2e28c300ca6 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:30:37 +0100 Subject: [PATCH 270/337] fix typos --- contracts/main/CurveStableSwapMetaNG.vy | 16 ++++++++-------- contracts/main/CurveStableSwapNG.vy | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index d8f067a1..c4166a38 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -582,7 +582,7 @@ def exchange( @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index value of the coin to recieve + @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -616,7 +616,7 @@ def exchange_received( directly to the contract and call `exchange_received`. Note: This is disabled if pool contains rebasing tokens. @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -1371,7 +1371,7 @@ def upkeep_oracles(xp: uint256[N_COINS], amp: uint256, D: uint256): # Metapools are always 2-coin pools, so we care about idx=0 only: if spot_price != 0: - # Upate packed prices ----------------- + # Update packed prices ----------------- last_prices_packed_new = self.pack_2( min(spot_price, 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( @@ -1637,7 +1637,7 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @notice Calculate the current input dx given output dy @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ @@ -1652,7 +1652,7 @@ def get_dx_underlying(i: int128, j: int128, dy: uint256) -> uint256: @dev Swap involves base pool tokens (either i or j should be 0); If not, this method reverts. @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ @@ -1666,7 +1666,7 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @notice Calculate the current output dy given input dx @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ @@ -1681,7 +1681,7 @@ def get_dy_underlying(i: int128, j: int128, dx: uint256) -> uint256: @dev Swap involves base pool tokens (either i or j should be 0); If not, this method reverts. @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ @@ -1792,7 +1792,7 @@ def dynamic_fee(i: int128, j: int128) -> uint256: """ @notice Return the fee for swapping between `i` and `j` @param i Index value for the coin to send - @param j Index value of the coin to recieve + @param j Index value of the coin to receive @return Swap fee expressed as an integer with 1e10 precision """ return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 91ca9bd0..bd8581e4 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -504,7 +504,7 @@ def exchange( @notice Perform an exchange between two coins @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index value of the coin to recieve + @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -538,7 +538,7 @@ def exchange_received( directly to the contract and call `exchange_received`. Note: This is disabled if pool contains rebasing tokens. @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive @return Actual amount of `j` received @@ -1346,7 +1346,7 @@ def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): if spot_price[i] != 0: - # Upate packed prices ----------------- + # Update packed prices ----------------- last_prices_packed_new[i] = self.pack_2( min(spot_price[i], 2 * 10**18), # <----- Cap spot value by 2. self._calc_moving_average( @@ -1680,7 +1680,7 @@ def get_dx(i: int128, j: int128, dy: uint256) -> uint256: @notice Calculate the current input dx given output dy @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dy Amount of `j` being received after exchange @return Amount of `i` predicted """ @@ -1694,7 +1694,7 @@ def get_dy(i: int128, j: int128, dx: uint256) -> uint256: @notice Calculate the current output dy given input dx @dev Index values can be found via the `coins` public getter method @param i Index value for the coin to send - @param j Index valie of the coin to recieve + @param j Index value of the coin to receive @param dx Amount of `i` being exchanged @return Amount of `j` predicted """ @@ -1802,7 +1802,7 @@ def dynamic_fee(i: int128, j: int128) -> uint256: """ @notice Return the fee for swapping between `i` and `j` @param i Index value for the coin to send - @param j Index value of the coin to recieve + @param j Index value of the coin to receive @return Swap fee expressed as an integer with 1e10 precision """ return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) From 9bace0dab57716e6b73784fe4956cdc846e10ce3 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:34:33 +0100 Subject: [PATCH 271/337] add some natspec --- contracts/main/CurveStableSwapMetaNG.vy | 8 +++++++- contracts/main/CurveStableSwapNG.vy | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index c4166a38..5c1e567c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -481,6 +481,9 @@ def _transfer_out( ): """ @notice Transfer a single token from the pool to receiver. + @dev This function is called by `remove_liquidity` and + `remove_liquidity_one_coin`, `_exchange`, `_withdraw_admin_fees` and + `remove_liquidity_imbalance` methods. @param _coin_idx Index of the token to transfer out @param _amount Amount of token to transfer out @param receiver Address to send the tokens to @@ -585,6 +588,7 @@ def exchange( @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive + @param _receiver Address that receives `j` @return Actual amount of `j` received """ return self._exchange( @@ -619,6 +623,7 @@ def exchange_received( @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive + @param _receiver Address that receives `j` @return Actual amount of `j` received """ assert asset_type != 2 # dev: exchange_received not supported if pool contains rebasing tokens @@ -1858,7 +1863,8 @@ def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): """ @notice Set the moving average window of the price oracles. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + @param _ma_exp_time Moving average window for the price oracle. It is time_in_seconds / ln(2). + @param _D_ma_time Moving average window for the D oracle. It is time_in_seconds / ln(2). """ assert msg.sender == factory.admin() # dev: only owner assert unsafe_mul(_ma_exp_time, _D_ma_time) > 0 # dev: 0 in input values diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index bd8581e4..b3ad05bd 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -361,7 +361,6 @@ def _transfer_in( @notice Contains all logic to handle ERC20 token transfers. @param coin_idx Index of the coin to transfer in. @param dx amount of `_coin` to transfer into the pool. - @param dy amount of `_coin` to transfer out of the pool. @param sender address to transfer `_coin` from. @param receiver address to transfer `_coin` to. @param expect_optimistic_transfer True if contract expects an optimistic coin transfer @@ -396,7 +395,8 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): """ @notice Transfer a single token from the pool to receiver. @dev This function is called by `remove_liquidity` and - `remove_liquidity_one`, `_exchange` and `_withdraw_admin_fees` methods. + `remove_liquidity_one_coin`, `_exchange`, `_withdraw_admin_fees` and + `remove_liquidity_imbalance` methods. @param _coin_idx Index of the token to transfer out @param _amount Amount of token to transfer out @param receiver Address to send the tokens to @@ -507,6 +507,7 @@ def exchange( @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive + @param _receiver Address that receives `j` @return Actual amount of `j` received """ return self._exchange( @@ -541,6 +542,7 @@ def exchange_received( @param j Index value of the coin to receive @param _dx Amount of `i` being exchanged @param _min_dy Minimum amount of `j` to receive + @param _receiver Address that receives `j` @return Actual amount of `j` received """ assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens @@ -1868,7 +1870,8 @@ def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): """ @notice Set the moving average window of the price oracles. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) + @param _ma_exp_time Moving average window for the price oracle. It is time_in_seconds / ln(2). + @param _D_ma_time Moving average window for the D oracle. It is time_in_seconds / ln(2). """ assert msg.sender == factory.admin() # dev: only owner assert unsafe_mul(_ma_exp_time, _D_ma_time) > 0 # dev: 0 in input values From 4b3cb6d7fa9762102e3884a47491d98e90e8aa63 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:45:44 +0100 Subject: [PATCH 272/337] allow ng pools as base pools --- contracts/main/CurveStableSwapMetaNG.vy | 24 ++- contracts/main/CurveStableSwapNG.vy | 2 +- contracts/main/CurveStableSwapNGViews.vy | 8 +- poetry.lock | 8 +- pyproject.toml | 2 +- tests/conftest.py | 12 +- tests/gauge/test_rewards.py | 2 +- tests/pools/meta/test_meta_new_ng_base.py | 177 +++++++++++++++++++ tests/pools/meta/test_revert_meta_ng_base.py | 132 -------------- tests/test_get_D.py | 60 +++++++ 10 files changed, 278 insertions(+), 149 deletions(-) create mode 100644 tests/pools/meta/test_meta_new_ng_base.py delete mode 100644 tests/pools/meta/test_revert_meta_ng_base.py create mode 100644 tests/test_get_D.py diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 5c1e567c..d3b1e47a 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -89,6 +89,12 @@ interface StableSwap2: interface StableSwap3: def add_liquidity(amounts: uint256[3], min_mint_amount: uint256): nonpayable +interface StableSwapNG: + def add_liquidity( + amounts: DynArray[uint256, MAX_COINS], + min_mint_amount: uint256 + ) -> uint256: nonpayable + interface StableSwap: def remove_liquidity_one_coin(_token_amount: uint256, i: int128, min_amount: uint256): nonpayable def exchange(i: int128, j: int128, dx: uint256, min_dy: uint256): nonpayable @@ -201,6 +207,7 @@ N_COINS_128: constant(int128) = 2 PRECISION: constant(uint256) = 10 ** 18 BASE_POOL: public(immutable(address)) +BASE_POOL_IS_NG: immutable(bool) BASE_N_COINS: public(immutable(uint256)) BASE_COINS: public(immutable(DynArray[address, MAX_COINS])) @@ -326,10 +333,11 @@ def __init__( Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] @param _oracles Array of rate oracle addresses. """ - assert len(_base_coins) <= 3 # dev: implementation does not support base pool with more than 3 coins - # The following reverts if BASE_POOL is an NG implementaion. - assert not raw_call(_base_pool, method_id("D_ma_time()"), revert_on_failure=False) + BASE_POOL_IS_NG = raw_call(_base_pool, method_id("D_ma_time()"), revert_on_failure=False) + + if not BASE_POOL_IS_NG: + assert len(_base_coins) <= 3 # dev: implementation does not support old gen base pool with more than 3 coins math = Math(_math_implementation) BASE_POOL = _base_pool @@ -1179,6 +1187,16 @@ def _exchange( @internal def _meta_add_liquidity(dx: uint256, base_i: int128) -> uint256: + if BASE_POOL_IS_NG: + + base_inputs: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(BASE_N_COINS, bound=MAX_COINS): + if i == convert(base_i, uint256): + base_inputs.append(dx) + else: + base_inputs.append(0) + return StableSwapNG(BASE_POOL).add_liquidity(base_inputs, 0) + coin_i: address = coins[MAX_METAPOOL_COIN_INDEX] x: uint256 = ERC20(coin_i).balanceOf(self) diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index b3ad05bd..f4f32c10 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1091,7 +1091,7 @@ def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: D_P: uint256 = D for x in _xp: - D_P *= D / x + D_P = D_P * D / x D_P /= pow_mod256(N_COINS, N_COINS) Dprev: uint256 = D diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 183355dc..6ff702dd 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -471,7 +471,13 @@ def _base_calc_token_amount( else: - raise "base_n_coins > 3 not supported yet." + base_inputs: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + for i in range(base_n_coins, bound=MAX_COINS): + if i == convert(base_i, uint256): + base_inputs.append(dx) + else: + base_inputs.append(0) + return StableSwapNG(base_pool).calc_token_amount(base_inputs, is_deposit) @internal diff --git a/poetry.lock b/poetry.lock index a40132fa..56d0244a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2873,9 +2873,9 @@ forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/vyperlang/titanoboa" -reference = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47" -resolved_reference = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47" +url = "https://github.com/vyperlang/titanoboa.git" +reference = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0" +resolved_reference = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0" [[package]] name = "tomli" @@ -3187,4 +3187,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "852e23bdada5987fdd4245dafa312bea5a6cfd683e2afe5aece58bdc9ff973ea" +content-hash = "b11f60d84eea1a9d63bb0c5dafd5e0193d75b0a01843d27011084e447fed6e27" diff --git a/pyproject.toml b/pyproject.toml index 4c9131f8..a0e1397f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa", rev = "ce6c65ac8d4c7c208a06cb2a06f07e65d4ce9f47"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/conftest.py b/tests/conftest.py index 531da375..13e14396 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -170,7 +170,7 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals): # # @pytest.mark.only_for_token_types(2) # class TestPoolsWithOracleToken: -@pytest.fixture(autouse=True) +@pytest.fixture() def skip_by_token_type(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: @@ -179,7 +179,7 @@ def skip_by_token_type(request, pool_tokens): pytest.skip("skipped because no tokens for these types") -@pytest.fixture(autouse=True) +@pytest.fixture() def skip_rebasing(request, swap): only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") if only_for_token_types: @@ -187,7 +187,7 @@ def skip_rebasing(request, swap): pytest.skip("skipped because test includes rebasing tokens") -@pytest.fixture(autouse=True) +@pytest.fixture() def skip_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") if only_for_token_types: @@ -197,7 +197,7 @@ def skip_oracle(request, pool_tokens): pytest.skip("skipped because test includes oraclised tokens") -@pytest.fixture(autouse=True) +@pytest.fixture() def only_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") if only_for_token_types: @@ -207,7 +207,7 @@ def only_oracle(request, pool_tokens): pytest.skip("skipped because test excludes oraclised tokens") -@pytest.fixture(autouse=True) +@pytest.fixture() def only_rebasing(request, swap): marker = request.node.get_closest_marker("contains_rebasing_tokens") if marker: @@ -219,7 +219,7 @@ def only_rebasing(request, swap): # Usage # @pytest.mark.only_for_pool_type(1) # class TestMetaPool... -@pytest.fixture(autouse=True) +@pytest.fixture() def skip_by_pool_type(request, pool_type): only_for_pool_type = request.node.get_closest_marker("only_for_pool_type") if only_for_pool_type: diff --git a/tests/gauge/test_rewards.py b/tests/gauge/test_rewards.py index a9554cd7..450ee3f8 100644 --- a/tests/gauge/test_rewards.py +++ b/tests/gauge/test_rewards.py @@ -9,7 +9,7 @@ @pytest.mark.usefixtures("forked_chain") class TestGaugeRewards: class TestAddRewards: - @pytest.fixture(autouse=True) + @pytest.fixture() def initial_setup(self, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation): with boa.env.prank(owner): swap.approve(gauge.address, LP_AMOUNT) diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py new file mode 100644 index 00000000..778d3ce5 --- /dev/null +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -0,0 +1,177 @@ +import itertools + +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + +BASE_N_COINS = 5 + + +@pytest.fixture(scope="module") +def ng_base_pool_decimals(): + return [18] * BASE_N_COINS + + +@pytest.fixture(scope="module") +def ng_base_pool_tokens(ng_base_pool_decimals): + tokens = [] + for i in range(BASE_N_COINS): + tokens.append(boa.load("contracts/mocks/ERC20.vy", f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i])) + + return tokens + + +@pytest.fixture(scope="module") +def meta_token(): + return boa.load( + "contracts/mocks/ERC20.vy", + "OTA", + "OTA", + 18, + ) + + +@pytest.fixture(scope="module") +def ng_base_pool( + deployer, + factory, + ng_base_pool_tokens, + zero_address, + amm_interface, + set_pool_implementations, +): + pool_size = len(ng_base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in ng_base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + [tkn.asset_type() for tkn in ng_base_pool_tokens], + method_ids, + oracles, + ) + + return amm_interface.at(pool) + + +@pytest.fixture(scope="module") +def ng_metapool_tokens(meta_token, ng_base_pool): + return [meta_token, ng_base_pool] + + +@pytest.fixture(scope="module") +def add_ng_base_pool( + owner, + factory, + ng_base_pool, + ng_base_pool_tokens, +): + with boa.env.prank(owner): + factory.add_base_pool( + ng_base_pool.address, + ng_base_pool.address, + [0] * len(ng_base_pool_tokens), + len(ng_base_pool_tokens), + ) + + +@pytest.fixture(scope="module") +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + ng_base_pool, + amm_interface_meta, + add_ng_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + ng_base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + meta_token.asset_type(), # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return amm_interface_meta.at(pool) + + +@pytest.fixture(scope="module") +def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, ng_base_pool): + for token in [meta_token] + ng_base_pool_tokens: + mint_for_testing(bob, 10**25, token) + token.approve(empty_swap, 2**256 - 1, sender=bob) + token.approve(ng_base_pool, 2**256 - 1, sender=bob) + + +@pytest.fixture(scope="module") +def deposit_amounts( + meta_token, + ng_base_pool, + ng_base_pool_tokens, + ng_base_pool_decimals, + empty_swap, + bob, + mint_and_approve_for_bob, +): + _deposit_amounts = [] + INITIAL_AMOUNT = 1_000_000 * BASE_N_COINS + _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** meta_token.decimals()) + + def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = INITIAL_AMOUNT // BASE_N_COINS + with boa.env.prank(user): + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) + + add_base_pool_liquidity(bob, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals) + _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** ng_base_pool.decimals()) + ng_base_pool.approve(empty_swap, 2**256 - 1, sender=bob) + return _deposit_amounts + + +@pytest.fixture(scope="module") +def swap(empty_swap, bob, deposit_amounts): + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +def test_exchange_underlying_ng_base( + swap, + bob, + sending, + receiving, +): + amount = 10**19 + expected_out = swap.get_dy_underlying(sending, receiving, amount) + actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + + assert expected_out == actual_out diff --git a/tests/pools/meta/test_revert_meta_ng_base.py b/tests/pools/meta/test_revert_meta_ng_base.py deleted file mode 100644 index 82f2d8df..00000000 --- a/tests/pools/meta/test_revert_meta_ng_base.py +++ /dev/null @@ -1,132 +0,0 @@ -import boa -import pytest - - -@pytest.fixture(scope="module") -def borky_factory( - deployer, - fee_receiver, - owner, - gauge_implementation, - views_implementation, - math_implementation, - amm_implementation, - amm_implementation_meta, -): - with boa.env.prank(deployer): - _factory = boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) - - with boa.env.prank(owner): - _factory.set_gauge_implementation(gauge_implementation.address) - _factory.set_views_implementation(views_implementation.address) - _factory.set_math_implementation(math_implementation.address) - _factory.set_pool_implementations(0, amm_implementation.address) - _factory.set_metapool_implementations(0, amm_implementation_meta.address) - - return _factory - - -# <--------------------- Functions ---------------------> - - -@pytest.fixture(scope="module") -def ng_base_pool_decimals(): - return [18, 18] - - -@pytest.fixture(scope="module") -def ng_base_pool_tokens(deployer, ng_base_pool_decimals): - tokens = [] - with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", ng_base_pool_decimals[0])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", ng_base_pool_decimals[1])) - - return tokens - - -@pytest.fixture(scope="module") -def ng_base_pool( - deployer, - borky_factory, - ng_base_pool_tokens, - zero_address, - amm_interface, -): - asset_types = [0, 0] - pool_size = len(ng_base_pool_tokens) - offpeg_fee_multiplier = 20000000000 - method_ids = [bytes(b"")] * pool_size - oracles = [zero_address] * pool_size - A = 1000 - fee = 3000000 - - with boa.env.prank(deployer): - pool = borky_factory.deploy_plain_pool( - "test", - "test", - [t.address for t in ng_base_pool_tokens], - A, - fee, - offpeg_fee_multiplier, - 866, - 0, - asset_types, - method_ids, - oracles, - ) - - return amm_interface.at(pool) - - -@pytest.fixture(scope="module") -def add_ng_base_pool( - owner, - borky_factory, - ng_base_pool, - ng_base_pool_tokens, -): - with boa.env.prank(owner): - borky_factory.add_base_pool( - ng_base_pool.address, - ng_base_pool.address, - [0] * len(ng_base_pool_tokens), - len(ng_base_pool_tokens), - ) - - -def test_revert_metapool_deployment_with_ng_base_pool( - deployer, borky_factory, zero_address, add_ng_base_pool, ng_base_pool -): - method_id = bytes(b"") - oracle = zero_address - offpeg_fee_multiplier = 20000000000 - A = 1000 - fee = 3000000 - meta_token = boa.load( - "contracts/mocks/ERC20.vy", - "OTA", - "OTA", - 18, - ) - asset_type = meta_token.asset_type() - - with boa.reverts(): - borky_factory.deploy_metapool( - ng_base_pool.address, # _base_pool: address - "test", # _name: String[32], - "test", # _symbol: String[10], - meta_token.address, # _coin: address, - A, # _A: uint256, - fee, # _fee: uint256, - offpeg_fee_multiplier, - 866, # _ma_exp_time: uint256, - 0, # _implementation_idx: uint256 - asset_type, # _asset_type: uint8 - method_id, # _method_id: bytes4 - oracle, # _oracle: address - sender=deployer, - ) diff --git a/tests/test_get_D.py b/tests/test_get_D.py new file mode 100644 index 00000000..999c0fe0 --- /dev/null +++ b/tests/test_get_D.py @@ -0,0 +1,60 @@ +import boa +import pytest + + +@pytest.fixture(scope="module") +def new_math(): + + return boa.loads( + """ +A_PRECISION: constant(uint256) = 100 +N_COINS: constant(uint256) = 2 +MAX_COINS: constant(uint256) = 8 + +@pure +@external +def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: + + S: uint256 = 0 + for x in _xp: + S += x + if S == 0: + return 0 + + D: uint256 = S + Ann: uint256 = _amp * N_COINS + + for i in range(255): + + D_P: uint256 = D + for x in _xp: + D_P = D_P * D / x + D_P /= pow_mod256(N_COINS, N_COINS) + Dprev: uint256 = D + + # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) + D = ( + (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * D + / + ( + unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + + unsafe_add(N_COINS, 1) * D_P + ) + ) + + # Equality with the precision of 1 + if D > Dprev: + if D - Dprev <= 1: + return D + else: + if Dprev - D <= 1: + return D + # convergence typically occurs in 4 rounds or less, this should be unreachable! + # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` + raise +""" + ) + + +def test_convergence(new_math): + assert new_math.get_D([10**23, 10**18], 1000) == 9010395375710532394006 From 06b398f482ef8c3adf0da1ae3c7ce88c5fabf07a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:37:35 +0100 Subject: [PATCH 273/337] fix marker fixtures to autouse; set filterwarning in pytest config --- pyproject.toml | 3 +++ tests/conftest.py | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a0e1397f..7c62505b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,3 +67,6 @@ markers = [ "only_for_token_types: running tests only if tokens of specific types are in pool", "no_auto_generate: dont generate tests for this one", ] +filterwarnings = [ + "ignore:PytestUnknownMarkWarning" +] diff --git a/tests/conftest.py b/tests/conftest.py index 13e14396..ec03b6fd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -170,7 +170,7 @@ def meta_decimals(initial_decimals, metapool_token_type, decimals): # # @pytest.mark.only_for_token_types(2) # class TestPoolsWithOracleToken: -@pytest.fixture() +@pytest.fixture(autouse=True) def skip_by_token_type(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_for_token_types") if only_for_token_types: @@ -179,7 +179,7 @@ def skip_by_token_type(request, pool_tokens): pytest.skip("skipped because no tokens for these types") -@pytest.fixture() +@pytest.fixture(autouse=True) def skip_rebasing(request, swap): only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") if only_for_token_types: @@ -187,7 +187,7 @@ def skip_rebasing(request, swap): pytest.skip("skipped because test includes rebasing tokens") -@pytest.fixture() +@pytest.fixture(autouse=True) def skip_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") if only_for_token_types: @@ -197,7 +197,7 @@ def skip_oracle(request, pool_tokens): pytest.skip("skipped because test includes oraclised tokens") -@pytest.fixture() +@pytest.fixture(autouse=True) def only_oracle(request, pool_tokens): only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") if only_for_token_types: @@ -207,7 +207,7 @@ def only_oracle(request, pool_tokens): pytest.skip("skipped because test excludes oraclised tokens") -@pytest.fixture() +@pytest.fixture(autouse=True) def only_rebasing(request, swap): marker = request.node.get_closest_marker("contains_rebasing_tokens") if marker: @@ -219,7 +219,7 @@ def only_rebasing(request, swap): # Usage # @pytest.mark.only_for_pool_type(1) # class TestMetaPool... -@pytest.fixture() +@pytest.fixture(autouse=True) def skip_by_pool_type(request, pool_type): only_for_pool_type = request.node.get_closest_marker("only_for_pool_type") if only_for_pool_type: @@ -227,7 +227,7 @@ def skip_by_pool_type(request, pool_type): pytest.skip("skipped because another pool type") -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") assert rpc_url is not None, "Provider url is not set, add WEB3_PROVIDER_URL param to env" From 1fb6428d53858757f15f1e4872d45cd0f154b81f Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:51:12 +0100 Subject: [PATCH 274/337] remove A_oracle and focus on that on a different PR --- contracts/main/CurveStableSwapNGAOracle.vy | 1837 -------------------- 1 file changed, 1837 deletions(-) delete mode 100644 contracts/main/CurveStableSwapNGAOracle.vy diff --git a/contracts/main/CurveStableSwapNGAOracle.vy b/contracts/main/CurveStableSwapNGAOracle.vy deleted file mode 100644 index dcad00cf..00000000 --- a/contracts/main/CurveStableSwapNGAOracle.vy +++ /dev/null @@ -1,1837 +0,0 @@ -# pragma version 0.3.10 -# pragma optimize codesize -# pragma evm-version shanghai -""" -@title CurveStableSwapNGAOracle -@author Curve.Fi -@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved -@notice Stableswap implementation for up to 8 coins with no rehypothecation, - i.e. the AMM does not deposit tokens into other contracts. The Pool contract also - records exponential moving averages for coins relative to coin 0. -@dev Asset Types: - 0. Standard ERC20 token with no additional features. - Note: Users are advised to do careful due-diligence on - ERC20 tokens that they interact with, as this - contract cannot differentiate between harmless and - malicious ERC20 tokens. - 1. Oracle - token with rate oracle (e.g. wstETH) - Note: Oracles may be controlled externally by an EOA. Users - are advised to proceed with caution. - 2. Rebasing - token with rebase (e.g. stETH). - Note: Users and Integrators are advised to understand how - the AMM contract works with rebasing balances. - 3. ERC4626 - token with convertToAssets method (e.g. sDAI). - Note: Some ERC4626 implementations may be susceptible to - Donation/Inflation attacks. Users are advised to - proceed with caution. - Supports: - 1. ERC20 support for return True/revert, return True/False, return None - 2. ERC20 tokens can have arbitrary decimals (<=18). - 3. ERC20 tokens that rebase (either positive or fee on transfer) - 4. ERC20 tokens that have a rate oracle (e.g. wstETH, cbETH, sDAI, etc.) - Note: Oracle precision _must_ be 10**18. - 5. ERC4626 tokens with arbitrary precision (<=18) of Vault token and underlying - asset. - Additional features include: - 1. Adds price oracles based on AMM State Price (and _not_ last traded price). - 2. Adds TVL oracle based on D. - 3. `exchange_received`: swaps that expect an ERC20 transfer to have occurred - prior to executing the swap. - Note: a. If pool contains rebasing tokens and one of the `asset_types` is 2 (Rebasing) - then calling `exchange_received` will REVERT. - b. If pool contains rebasing token and `asset_types` does not contain 2 (Rebasing) - then this is an incorrect implementation and rebases can be - stolen. - 4. Adds `get_dx`: Similar to `get_dy` which returns an expected output - of coin[j] for given `dx` amount of coin[i], `get_dx` returns expected - input of coin[i] for an output amount of coin[j]. - 5. Fees are dynamic: AMM will charge a higher fee if pool depegs. This can cause very - slight discrepancies between calculated fees and realised fees. - 6. The AMM contract queries Amplification factor from an external contract. This - external contract is set by the deployer of the pool. - Note: Users should do careful due diligence to ascertain that the pool owner address - is not malicious/buggy. -""" - -from vyper.interfaces import ERC20 -from vyper.interfaces import ERC20Detailed -from vyper.interfaces import ERC4626 - -implements: ERC20 - -# ------------------------------- Interfaces --------------------------------- - -interface Factory: - def fee_receiver() -> address: view - def admin() -> address: view - def views_implementation() -> address: view - -interface ERC1271: - def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: view - -interface StableSwapViews: - def get_dx(i: int128, j: int128, dy: uint256, pool: address) -> uint256: view - def get_dy(i: int128, j: int128, dx: uint256, pool: address) -> uint256: view - def dynamic_fee(i: int128, j: int128, pool: address) -> uint256: view - def calc_token_amount( - _amounts: DynArray[uint256, MAX_COINS], - _is_deposit: bool, - _pool: address - ) -> uint256: view - -# --------------------------------- Events ----------------------------------- - -event Transfer: - sender: indexed(address) - receiver: indexed(address) - value: uint256 - -event Approval: - owner: indexed(address) - spender: indexed(address) - value: uint256 - -event TokenExchange: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event TokenExchangeUnderlying: - buyer: indexed(address) - sold_id: int128 - tokens_sold: uint256 - bought_id: int128 - tokens_bought: uint256 - -event AddLiquidity: - provider: indexed(address) - token_amounts: DynArray[uint256, MAX_COINS] - fees: DynArray[uint256, MAX_COINS] - invariant: uint256 - token_supply: uint256 - -event RemoveLiquidity: - provider: indexed(address) - token_amounts: DynArray[uint256, MAX_COINS] - fees: DynArray[uint256, MAX_COINS] - token_supply: uint256 - -event RemoveLiquidityOne: - provider: indexed(address) - token_id: int128 - token_amount: uint256 - coin_amount: uint256 - token_supply: uint256 - -event RemoveLiquidityImbalance: - provider: indexed(address) - token_amounts: DynArray[uint256, MAX_COINS] - fees: DynArray[uint256, MAX_COINS] - invariant: uint256 - token_supply: uint256 - -event ApplyNewFee: - fee: uint256 - offpeg_fee_multiplier: uint256 - -event SetNewAOracle: - oracle: address - - -MAX_COINS: constant(uint256) = 8 # max coins is 8 in the factory -MAX_COINS_128: constant(int128) = 8 - -# ---------------------------- Pool Variables -------------------------------- - -POOL_MANAGER: public(immutable(address)) - -N_COINS: public(immutable(uint256)) -N_COINS_128: immutable(int128) -PRECISION: constant(uint256) = 10 ** 18 - -factory: immutable(Factory) -coins: public(immutable(DynArray[address, MAX_COINS])) -asset_types: immutable(DynArray[uint8, MAX_COINS]) -stored_balances: DynArray[uint256, MAX_COINS] - -# Fee specific vars -FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -fee: public(uint256) # fee * 1e10 -offpeg_fee_multiplier: public(uint256) # * 1e10 -admin_fee: public(constant(uint256)) = 5000000000 -MAX_FEE: constant(uint256) = 5 * 10 ** 9 - -# ---------------------- Pool Amplification Parameters ----------------------- - -A_PRECISION: constant(uint256) = 100 -MAX_A: constant(uint256) = 10 ** 6 - -# ---------------------------- Admin Variables ------------------------------- - -MIN_RAMP_TIME: constant(uint256) = 86400 -admin_balances: public(DynArray[uint256, MAX_COINS]) - -# ----------------------- Oracle Specific vars ------------------------------- - -rate_multipliers: immutable(DynArray[uint256, MAX_COINS]) -# [bytes4 method_id][bytes8 ][bytes20 oracle] -oracles: DynArray[uint256, MAX_COINS] -A_oracle: public(uint256) - -# For ERC4626 tokens, we need: -call_amount: immutable(DynArray[uint256, MAX_COINS]) -scale_factor: immutable(DynArray[uint256, MAX_COINS]) - -last_prices_packed: DynArray[uint256, MAX_COINS] # packing: last_price, ma_price -last_D_packed: uint256 # packing: last_D, ma_D -ma_exp_time: public(uint256) -D_ma_time: public(uint256) -ma_last_time: public(uint256) # packing: ma_last_time_p, ma_last_time_D -# ma_last_time has a distinction for p and D because p is _not_ updated if -# users remove_liquidity, but D is. - -# shift(2**32 - 1, 224) -ORACLE_BIT_MASK: constant(uint256) = (2**32 - 1) * 256**28 - -# --------------------------- ERC20 Specific Vars ---------------------------- - -name: public(immutable(String[64])) -symbol: public(immutable(String[32])) -decimals: public(constant(uint8)) = 18 -version: public(constant(String[8])) = "v7.0.0" - -balanceOf: public(HashMap[address, uint256]) -allowance: public(HashMap[address, HashMap[address, uint256]]) -total_supply: uint256 -nonces: public(HashMap[address, uint256]) - -# keccak256("isValidSignature(bytes32,bytes)")[:4] << 224 -ERC1271_MAGIC_VAL: constant(bytes32) = 0x1626ba7e00000000000000000000000000000000000000000000000000000000 -EIP712_TYPEHASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)") -EIP2612_TYPEHASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - -VERSION_HASH: constant(bytes32) = keccak256(version) -NAME_HASH: immutable(bytes32) -CACHED_CHAIN_ID: immutable(uint256) -salt: public(immutable(bytes32)) -CACHED_DOMAIN_SEPARATOR: immutable(bytes32) - - -# ------------------------------ AMM Setup ----------------------------------- - - -@external -def __init__( - _name: String[32], - _symbol: String[10], - _A: uint256, - _fee: uint256, - _offpeg_fee_multiplier: uint256, - _ma_exp_time: uint256, - _coins: DynArray[address, MAX_COINS], - _rate_multipliers: DynArray[uint256, MAX_COINS], - _asset_types: DynArray[uint8, MAX_COINS], - _method_ids: DynArray[bytes4, MAX_COINS], - _oracles: DynArray[address, MAX_COINS], -): - """ - @notice Initialize the pool contract - @param _name Name of the new plain pool. - @param _symbol Symbol for the new plain pool. - @param _A Amplification co-efficient. For this implementation, the constructor - argument is inconsequential since A is inferred from an external call. - @param _fee Trade fee, given as an integer with 1e10 precision. The - the maximum is 1% (100000000). - 50% of the fee is distributed to veCRV holders. - @param _offpeg_fee_multiplier A multiplier that determines how much to increase - Fees by when assets in the AMM depeg. Example value: 20000000000 - @param _ma_exp_time Averaging window of oracle. Set as time_in_seconds / ln(2) - Example: for 10 minute EMA, _ma_exp_time is 600 / ln(2) ~= 866 - @param _coins List of addresses of the coins being used in the pool. - @param _rate_multipliers An array of: [10 ** (36 - _coins[n].decimals()), ... for n in range(N_COINS)] - @param _asset_types Array of uint8 representing tokens in pool - @param _method_ids Array of first four bytes of the Keccak-256 hash of the function signatures - of the oracle addresses that gives rate oracles. - Calculated as: keccak(text=event_signature.replace(" ", ""))[:4] - @param _oracles Array of rate oracle addresses. - """ - - POOL_MANAGER = tx.origin - - coins = _coins - asset_types = _asset_types - __n_coins: uint256 = len(_coins) - N_COINS = __n_coins - N_COINS_128 = convert(__n_coins, int128) - - rate_multipliers = _rate_multipliers - - factory = Factory(msg.sender) - - self.fee = _fee - self.offpeg_fee_multiplier = _offpeg_fee_multiplier - - assert _ma_exp_time != 0 - self.ma_exp_time = _ma_exp_time - self.D_ma_time = 62324 # <--------- 12 hours default on contract start. - self.ma_last_time = self.pack_2(block.timestamp, block.timestamp) - - # ------------------- initialize storage for DynArrays ------------------ - - _call_amount: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - _scale_factor: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if i < N_COINS_128 - 1: - self.last_prices_packed.append(self.pack_2(10**18, 10**18)) - - self.oracles.append(convert(_method_ids[i], uint256) * 2**224 | convert(_oracles[i], uint256)) - self.stored_balances.append(0) - self.admin_balances.append(0) - - if _asset_types[i] == 3: - - _call_amount.append(10**convert(ERC20Detailed(_coins[i]).decimals(), uint256)) - _underlying_asset: address = ERC4626(_coins[i]).asset() - _scale_factor.append(10**(18 - convert(ERC20Detailed(_underlying_asset).decimals(), uint256))) - - else: - - _call_amount.append(0) - _scale_factor.append(0) - - call_amount = _call_amount - scale_factor = _scale_factor - - # ----------------------------- ERC20 stuff ------------------------------ - - name = _name - symbol = _symbol - - # EIP712 related params ----------------- - NAME_HASH = keccak256(name) - salt = block.prevhash - CACHED_CHAIN_ID = chain.id - CACHED_DOMAIN_SEPARATOR = keccak256( - _abi_encode( - EIP712_TYPEHASH, - NAME_HASH, - VERSION_HASH, - chain.id, - self, - salt, - ) - ) - - # ------------------------ Fire a transfer event ------------------------- - - log Transfer(empty(address), msg.sender, 0) - - -# ------------------ Token transfers in and out of the AMM ------------------- - - -@internal -def _transfer_in( - coin_idx: int128, - dx: uint256, - sender: address, - expect_optimistic_transfer: bool, -) -> uint256: - """ - @notice Contains all logic to handle ERC20 token transfers. - @param coin_idx Index of the coin to transfer in. - @param dx amount of `_coin` to transfer into the pool. - @param dy amount of `_coin` to transfer out of the pool. - @param sender address to transfer `_coin` from. - @param receiver address to transfer `_coin` to. - @param expect_optimistic_transfer True if contract expects an optimistic coin transfer - """ - _dx: uint256 = ERC20(coins[coin_idx]).balanceOf(self) - - # ------------------------- Handle Transfers ----------------------------- - - if expect_optimistic_transfer: - - _dx = _dx - self.stored_balances[coin_idx] - assert _dx >= dx - - else: - - assert dx > 0 # dev : do not transferFrom 0 tokens into the pool - assert ERC20(coins[coin_idx]).transferFrom( - sender, self, dx, default_return_value=True - ) - - _dx = ERC20(coins[coin_idx]).balanceOf(self) - _dx - - # --------------------------- Store transferred in amount --------------------------- - - self.stored_balances[coin_idx] += _dx - - return _dx - - -@internal -def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): - """ - @notice Transfer a single token from the pool to receiver. - @dev This function is called by `remove_liquidity` and - `remove_liquidity_one`, `_exchange` and `_withdraw_admin_fees` methods. - @param _coin_idx Index of the token to transfer out - @param _amount Amount of token to transfer out - @param receiver Address to send the tokens to - """ - - coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) - - # ------------------------- Handle Transfers ----------------------------- - - assert ERC20(coins[_coin_idx]).transfer( - receiver, _amount, default_return_value=True - ) - - # ----------------------- Update Stored Balances ------------------------- - - self.stored_balances[_coin_idx] = coin_balance - _amount - - -# -------------------------- AMM Special Methods ----------------------------- - - -@view -@internal -def _stored_rates() -> DynArray[uint256, MAX_COINS]: - """ - @notice Gets rate multipliers for each coin. - @dev If the coin has a rate oracle that has been properly initialised, - this method queries that rate by static-calling an external - contract. - """ - rates: DynArray[uint256, MAX_COINS] = rate_multipliers - oracles: DynArray[uint256, MAX_COINS] = self.oracles - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if asset_types[i] == 1 and not oracles[i] == 0: - - # NOTE: fetched_rate is assumed to be 10**18 precision - fetched_rate: uint256 = convert( - raw_call( - convert(oracles[i] % 2**160, address), - _abi_encode(oracles[i] & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ), - uint256 - ) - - rates[i] = unsafe_div(rates[i] * fetched_rate, PRECISION) - - elif asset_types[i] == 3: # ERC4626 - - # fetched_rate: uint256 = ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i] - # here: call_amount has ERC4626 precision, but the returned value is scaled up to 18 - # using scale_factor which is (18 - n) if underlying asset has n decimals. - rates[i] = unsafe_div( - rates[i] * ERC4626(coins[i]).convertToAssets(call_amount[i]) * scale_factor[i], - PRECISION - ) # 1e18 precision - - return rates - - -@view -@internal -def _balances() -> DynArray[uint256, MAX_COINS]: - """ - @notice Calculates the pool's balances _excluding_ the admin's balances. - @dev If the pool contains rebasing tokens, this method ensures LPs keep all - rebases and admin only claims swap fees. This also means that, since - admin's balances are stored in an array and not inferred from read balances, - the fees in the rebasing token that the admin collects is immune to - slashing events. - """ - result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances_i: uint256 = 0 - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if 2 in asset_types: - balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] - else: - balances_i = self.stored_balances[i] - self.admin_balances[i] - - result.append(balances_i) - - return result - - -# -------------------------- AMM Main Functions ------------------------------ - - -@external -@nonreentrant('lock') -def exchange( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two coins - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index value of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - return self._exchange( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - False - ) - - -@external -@nonreentrant('lock') -def exchange_received( - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Perform an exchange between two coins without transferring token in - @dev The contract swaps tokens based on a change in balance of coin[i]. The - dx = ERC20(coin[i]).balanceOf(self) - self.stored_balances[i]. Users of - this method are dex aggregators, arbitrageurs, or other users who do not - wish to grant approvals to the contract: they would instead send tokens - directly to the contract and call `exchange_received`. - Note: This is disabled if pool contains rebasing tokens. - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param _dx Amount of `i` being exchanged - @param _min_dy Minimum amount of `j` to receive - @return Actual amount of `j` received - """ - assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens - return self._exchange( - msg.sender, - i, - j, - _dx, - _min_dy, - _receiver, - True, # <--------------------------------------- swap optimistically. - ) - - -@external -@nonreentrant('lock') -def add_liquidity( - _amounts: DynArray[uint256, MAX_COINS], - _min_mint_amount: uint256, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Deposit coins into the pool - @param _amounts List of amounts of coins to deposit - @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit - @param _receiver Address that owns the minted LP tokens - @return Amount of LP tokens received by depositing - """ - amp: uint256 = self._A() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - - # Initial invariant - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - - total_supply: uint256 = self.total_supply - new_balances: DynArray[uint256, MAX_COINS] = old_balances - - # -------------------------- Do Transfers In ----------------------------- - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if _amounts[i] > 0: - - new_balances[i] += self._transfer_in( - i, - _amounts[i], - msg.sender, - False, # expect_optimistic_transfer - ) - - else: - - assert total_supply != 0 # dev: initial deposit requires all coins - - # ------------------------------------------------------------------------ - - # Invariant after change - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - assert D1 > D0 - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - mint_amount: uint256 = 0 - - if total_supply > 0: - - ideal_balance: uint256 = 0 - difference: uint256 = 0 - new_balance: uint256 = 0 - - ys: uint256 = (D0 + D1) / N_COINS - xs: uint256 = 0 - _dynamic_fee_i: uint256 = 0 - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - ideal_balance = D1 * old_balances[i] / D0 - difference = 0 - new_balance = new_balances[i] - - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - - # fee[i] = _dynamic_fee(i, j) * difference / FEE_DENOMINATOR - xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) - _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee) - fees.append(_dynamic_fee_i * difference / FEE_DENOMINATOR) - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, new_balances) - D1 = self.get_D(xp, amp) # <--------------- Reuse D1 for new D value. - mint_amount = total_supply * (D1 - D0) / D0 - self.upkeep_oracles(xp, amp, D1) - - else: - - mint_amount = D1 # Take the dust if there was any - - # (re)instantiate D oracle if totalSupply is zero. - self.last_D_packed = self.pack_2(D1, D1) - - assert mint_amount >= _min_mint_amount, "Slippage screwed you" - - # Mint pool tokens - total_supply += mint_amount - self.balanceOf[_receiver] += mint_amount - self.total_supply = total_supply - log Transfer(empty(address), _receiver, mint_amount) - - log AddLiquidity(msg.sender, _amounts, fees, D1, total_supply) - - return mint_amount - - -@external -@nonreentrant('lock') -def remove_liquidity_one_coin( - _burn_amount: uint256, - i: int128, - _min_received: uint256, - _receiver: address = msg.sender, -) -> uint256: - """ - @notice Withdraw a single coin from the pool - @param _burn_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @param _min_received Minimum amount of coin to receive - @param _receiver Address that receives the withdrawn coins - @return Amount of coin received - """ - assert _burn_amount > 0 # dev: do not remove 0 LP tokens - dy: uint256 = 0 - fee: uint256 = 0 - xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - amp: uint256 = empty(uint256) - D: uint256 = empty(uint256) - - dy, fee, xp, amp, D = self._calc_withdraw_one_coin(_burn_amount, i) - assert dy >= _min_received, "Not enough coins removed" - - self.admin_balances[i] += fee * admin_fee / FEE_DENOMINATOR - - self._burnFrom(msg.sender, _burn_amount) - - self._transfer_out(i, dy, _receiver) - - log RemoveLiquidityOne(msg.sender, i, _burn_amount, dy, self.total_supply) - - self.upkeep_oracles(xp, amp, D) - - return dy - - -@external -@nonreentrant('lock') -def remove_liquidity_imbalance( - _amounts: DynArray[uint256, MAX_COINS], - _max_burn_amount: uint256, - _receiver: address = msg.sender -) -> uint256: - """ - @notice Withdraw coins from the pool in an imbalanced amount - @param _amounts List of amounts of underlying coins to withdraw - @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal - @param _receiver Address that receives the withdrawn coins - @return Actual amount of the LP token burned in the withdrawal - """ - amp: uint256 = self._A() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - D0: uint256 = self.get_D_mem(rates, old_balances, amp) - new_balances: DynArray[uint256, MAX_COINS] = old_balances - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if _amounts[i] != 0: - new_balances[i] -= _amounts[i] - self._transfer_out(i, _amounts[i], _receiver) - - D1: uint256 = self.get_D_mem(rates, new_balances, amp) - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - ys: uint256 = (D0 + D1) / N_COINS - - fees: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - dynamic_fee: uint256 = 0 - xs: uint256 = 0 - ideal_balance: uint256 = 0 - difference: uint256 = 0 - new_balance: uint256 = 0 - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - ideal_balance = D1 * old_balances[i] / D0 - difference = 0 - new_balance = new_balances[i] - - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - - xs = unsafe_div(rates[i] * (old_balances[i] + new_balance), PRECISION) - dynamic_fee = self._dynamic_fee(xs, ys, base_fee) - fees.append(dynamic_fee * difference / FEE_DENOMINATOR) - - self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR - new_balances[i] -= fees[i] - - D1 = self.get_D_mem(rates, new_balances, amp) # dev: reuse D1 for new D. - - self.upkeep_oracles(new_balances, amp, D1) - - total_supply: uint256 = self.total_supply - burn_amount: uint256 = ((D0 - D1) * total_supply / D0) + 1 - assert burn_amount > 1 # dev: zero tokens burned - assert burn_amount <= _max_burn_amount, "Slippage screwed you" - - total_supply -= burn_amount - self._burnFrom(msg.sender, burn_amount) - - log RemoveLiquidityImbalance(msg.sender, _amounts, fees, D1, total_supply) - - return burn_amount - - -@external -@nonreentrant('lock') -def remove_liquidity( - _burn_amount: uint256, - _min_amounts: DynArray[uint256, MAX_COINS], - _receiver: address = msg.sender, - _claim_admin_fees: bool = True, -) -> DynArray[uint256, MAX_COINS]: - """ - @notice Withdraw coins from the pool - @dev Withdrawal amounts are based on current deposit ratios - @param _burn_amount Quantity of LP tokens to burn in the withdrawal - @param _min_amounts Minimum amounts of underlying coins to receive - @param _receiver Address that receives the withdrawn coins - @return List of amounts of coins that were withdrawn - """ - total_supply: uint256 = self.total_supply - assert _burn_amount > 0 # dev: invalid burn amount - - amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - balances: DynArray[uint256, MAX_COINS] = self._balances() - - value: uint256 = 0 - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - value = balances[i] * _burn_amount / total_supply - assert value >= _min_amounts[i], "Withdrawal resulted in fewer coins than expected" - amounts.append(value) - self._transfer_out(i, value, _receiver) - - self._burnFrom(msg.sender, _burn_amount) # <---- Updates self.total_supply - - # --------------------------- Upkeep D_oracle ---------------------------- - - ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) - last_D_packed_current: uint256 = self.last_D_packed - old_D: uint256 = last_D_packed_current & (2**128 - 1) - - self.last_D_packed = self.pack_2( - old_D - unsafe_div(old_D * _burn_amount, total_supply), # new_D = proportionally reduce D. - self._calc_moving_average( - last_D_packed_current, - self.D_ma_time, - ma_last_time_unpacked[1] - ) - ) - - if ma_last_time_unpacked[1] < block.timestamp: - ma_last_time_unpacked[1] = block.timestamp - - self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) - - # ------------------------------- Log event ------------------------------ - - log RemoveLiquidity( - msg.sender, - amounts, - empty(DynArray[uint256, MAX_COINS]), - total_supply - _burn_amount - ) - - # ------- Withdraw admin fees if _claim_admin_fees is set to True -------- - if _claim_admin_fees: - self._withdraw_admin_fees() - - return amounts - - -@external -def withdraw_admin_fees(): - """ - @notice Claim admin fees. Callable by anyone. - """ - self._withdraw_admin_fees() - - -# ------------------------ AMM Internal Functions ---------------------------- - - -@view -@internal -def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256) -> uint256: - - _offpeg_fee_multiplier: uint256 = self.offpeg_fee_multiplier - if _offpeg_fee_multiplier <= FEE_DENOMINATOR: - return _fee - - xps2: uint256 = (xpi + xpj) ** 2 - return ( - (_offpeg_fee_multiplier * _fee) / - ((_offpeg_fee_multiplier - FEE_DENOMINATOR) * 4 * xpi * xpj / xps2 + FEE_DENOMINATOR) - ) - - -@internal -def __exchange( - x: uint256, - _xp: DynArray[uint256, MAX_COINS], - rates: DynArray[uint256, MAX_COINS], - i: int128, - j: int128, -) -> uint256: - - amp: uint256 = self._A() - D: uint256 = self.get_D(_xp, amp) - y: uint256 = self.get_y(i, j, x, _xp, amp, D) - - dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors - dy_fee: uint256 = dy * self._dynamic_fee((_xp[i] + x) / 2, (_xp[j] + y) / 2, self.fee) / FEE_DENOMINATOR - - # Convert all to real units - dy = (dy - dy_fee) * PRECISION / rates[j] - - self.admin_balances[j] += ( - dy_fee * admin_fee / FEE_DENOMINATOR - ) * PRECISION / rates[j] - - # Calculate and store state prices: - xp: DynArray[uint256, MAX_COINS] = _xp - xp[i] = x - xp[j] = y - # D is not changed because we did not apply a fee - self.upkeep_oracles(xp, amp, D) - - return dy - - -@internal -def _exchange( - sender: address, - i: int128, - j: int128, - _dx: uint256, - _min_dy: uint256, - receiver: address, - expect_optimistic_transfer: bool -) -> uint256: - - assert i != j # dev: coin index out of range - assert _dx > 0 # dev: do not exchange 0 coins - - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - old_balances: DynArray[uint256, MAX_COINS] = self._balances() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, old_balances) - - # --------------------------- Do Transfer in ----------------------------- - - # `dx` is whatever the pool received after ERC20 transfer: - dx: uint256 = self._transfer_in( - i, - _dx, - sender, - expect_optimistic_transfer - ) - - # ------------------------------- Exchange ------------------------------- - - x: uint256 = xp[i] + dx * rates[i] / PRECISION - dy: uint256 = self.__exchange(x, xp, rates, i, j) - assert dy >= _min_dy, "Exchange resulted in fewer coins than expected" - - # --------------------------- Do Transfer out ---------------------------- - - self._transfer_out(j, dy, receiver) - - # ------------------------------------------------------------------------ - - log TokenExchange(msg.sender, i, _dx, j, dy) - - return dy - - -@internal -def _withdraw_admin_fees(): - fee_receiver: address = factory.fee_receiver() - assert fee_receiver != empty(address) # dev: fee receiver not set - - admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - if admin_balances[i] > 0: - - self._transfer_out(i, admin_balances[i], fee_receiver) - admin_balances[i] = 0 - - self.admin_balances = admin_balances - - -# --------------------------- AMM Math Functions ----------------------------- - - -@view -@internal -def get_y( - i: int128, - j: int128, - x: uint256, - xp: DynArray[uint256, MAX_COINS], - _amp: uint256, - _D: uint256 -) -> uint256: - """ - Calculate x[j] if one makes x[i] = x - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i != j # dev: same coin - assert j >= 0 # dev: j below zero - assert j < N_COINS_128 # dev: j above N_COINS - - # should be unreachable, but good for safety - assert i >= 0 - assert i < N_COINS_128 - - amp: uint256 = _amp - D: uint256 = _D - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = amp * N_COINS - - for _i in range(MAX_COINS_128): - - if _i == N_COINS_128: - break - - if _i == i: - _x = x - elif _i != j: - _x = xp[_i] - else: - continue - - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann # - D - y: uint256 = D - - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@pure -@internal -def get_D(_xp: DynArray[uint256, MAX_COINS], _amp: uint256) -> uint256: - """ - D invariant calculation in non-overflowing integer operations - iteratively - - A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - - Converging solution: - D[j+1] = (A * n**n * sum(x_i) - D[j]**(n+1) / (n**n prod(x_i))) / (A * n**n - 1) - """ - S: uint256 = 0 - for x in _xp: - S += x - if S == 0: - return 0 - - D: uint256 = S - Ann: uint256 = _amp * N_COINS - D_P: uint256 = 0 - Dprev: uint256 = 0 - - for i in range(255): - - D_P = D - for x in _xp: - D_P = D_P * D / (x * N_COINS) - Dprev = D - - # (Ann * S / A_PRECISION + D_P * N_COINS) * D / ((Ann - A_PRECISION) * D / A_PRECISION + (N_COINS + 1) * D_P) - D = ( - (unsafe_div(Ann * S, A_PRECISION) + D_P * N_COINS) * - D / ( - unsafe_div((Ann - A_PRECISION) * D, A_PRECISION) + - unsafe_add(N_COINS, 1) * D_P - ) - ) - - # Equality with the precision of 1 - if D > Dprev: - if D - Dprev <= 1: - return D - else: - if Dprev - D <= 1: - return D - # convergence typically occurs in 4 rounds or less, this should be unreachable! - # if it does happen the pool is borked and LPs can withdraw via `remove_liquidity` - raise - - -@pure -@internal -def get_y_D( - A: uint256, - i: int128, - xp: DynArray[uint256, MAX_COINS], - D: uint256 -) -> uint256: - """ - Calculate x[i] if one reduces D from being calculated for xp to D - - Done by solving quadratic equation iteratively. - x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - x_1**2 + b*x_1 = c - - x_1 = (x_1**2 + c) / (2*x_1 + b) - """ - # x in the input is converted to the same price/precision - - assert i >= 0 # dev: i below zero - assert i < N_COINS_128 # dev: i above N_COINS - - S_: uint256 = 0 - _x: uint256 = 0 - y_prev: uint256 = 0 - c: uint256 = D - Ann: uint256 = A * N_COINS - - for _i in range(MAX_COINS_128): - - if _i == N_COINS_128: - break - - if _i != i: - _x = xp[_i] - else: - continue - S_ += _x - c = c * D / (_x * N_COINS) - - c = c * D * A_PRECISION / (Ann * N_COINS) - b: uint256 = S_ + D * A_PRECISION / Ann - y: uint256 = D - - for _i in range(255): - y_prev = y - y = (y*y + c) / (2 * y + b - D) - # Equality with the precision of 1 - if y > y_prev: - if y - y_prev <= 1: - return y - else: - if y_prev - y <= 1: - return y - raise - - -@view -@internal -def _A() -> uint256: - """ - @notice Queries an external contract for Amplification factor - @dev Since A is in an external contract, ramping A up or down is - a property of the external contract and not the AMM contract. - @return uint256 Fetched amplification factor - """ - A_oracle: uint256 = self.A_oracle - fetched_A: uint256 = convert( - raw_call( - convert(A_oracle % 2**160, address), - _abi_encode(A_oracle & ORACLE_BIT_MASK), - max_outsize=32, - is_static_call=True, - ), - uint256 - ) - - # Cap fetched_A between: [A_PRECISION, A_PRECISION * MAX_A]: - return min(max(A_PRECISION, fetched_A), A_PRECISION * MAX_A) - - -@pure -@internal -def _xp_mem( - _rates: DynArray[uint256, MAX_COINS], - _balances: DynArray[uint256, MAX_COINS] -) -> DynArray[uint256, MAX_COINS]: - - result: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(MAX_COINS_128): - if i == N_COINS_128: - break - result.append(_rates[i] * _balances[i] / PRECISION) - return result - - -@view -@internal -def get_D_mem( - _rates: DynArray[uint256, MAX_COINS], - _balances: DynArray[uint256, MAX_COINS], - _amp: uint256 -) -> uint256: - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(_rates, _balances) - return self.get_D(xp, _amp) - - -@view -@internal -def _calc_withdraw_one_coin( - _burn_amount: uint256, - i: int128 -) -> ( - uint256, - uint256, - DynArray[uint256, MAX_COINS], - uint256, - uint256 -): - # First, need to calculate - # * Get current D - # * Solve Eqn against y_i for D - _token_amount - amp: uint256 = self._A() - rates: DynArray[uint256, MAX_COINS] = self._stored_rates() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem(rates, self._balances()) - D0: uint256 = self.get_D(xp, amp) - - total_supply: uint256 = self.total_supply - D1: uint256 = D0 - _burn_amount * D0 / total_supply - new_y: uint256 = self.get_y_D(amp, i, xp, D1) - - base_fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1)) - ys: uint256 = (D0 + D1) / (2 * N_COINS) - xp_reduced: DynArray[uint256, MAX_COINS] = xp - - dx_expected: uint256 = 0 - xp_j: uint256 = 0 - xavg: uint256 = 0 - dynamic_fee: uint256 = 0 - - for j in range(MAX_COINS_128): - - if j == N_COINS_128: - break - - dx_expected = 0 - xp_j = xp[j] - - if j == i: - dx_expected = xp_j * D1 / D0 - new_y - xavg = (xp_j + new_y) / 2 - else: - dx_expected = xp_j - xp_j * D1 / D0 - xavg = xp_j - - dynamic_fee = self._dynamic_fee(xavg, ys, base_fee) - xp_reduced[j] = xp_j - dynamic_fee * dx_expected / FEE_DENOMINATOR - - dy: uint256 = xp_reduced[i] - self.get_y_D(amp, i, xp_reduced, D1) - dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees - dy = (dy - 1) * PRECISION / rates[i] # Withdraw less to account for rounding errors - - # update xp with new_y for p calculations. - xp[i] = new_y - - return dy, dy_0 - dy, xp, amp, D1 - - -# -------------------------- AMM Price Methods ------------------------------- - -@pure -@internal -def pack_2(p1: uint256, p2: uint256) -> uint256: - assert p1 < 2**128 - assert p2 < 2**128 - return p1 | (p2 << 128) - - -@pure -@internal -def unpack_2(packed: uint256) -> uint256[2]: - return [packed & (2**128 - 1), packed >> 128] - - -@internal -@pure -def _get_p( - xp: DynArray[uint256, MAX_COINS], - amp: uint256, - D: uint256, -) -> DynArray[uint256, MAX_COINS]: - - # dx_0 / dx_1 only, however can have any number of coins in pool - ANN: uint256 = unsafe_mul(amp, N_COINS) - Dr: uint256 = unsafe_div(D, pow_mod256(N_COINS, N_COINS)) - - for i in range(MAX_COINS_128): - - if i == N_COINS_128: - break - - Dr = Dr * D / xp[i] - - p: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - xp0_A: uint256 = ANN * xp[0] / A_PRECISION - - for i in range(1, MAX_COINS): - - if i == N_COINS: - break - - p.append(10**18 * (xp0_A + Dr * xp[0] / xp[i]) / (xp0_A + Dr)) - - return p - - -@internal -def upkeep_oracles(xp: DynArray[uint256, MAX_COINS], amp: uint256, D: uint256): - """ - @notice Upkeeps price and D oracles. - """ - ma_last_time_unpacked: uint256[2] = self.unpack_2(self.ma_last_time) - last_prices_packed_current: DynArray[uint256, MAX_COINS] = self.last_prices_packed - last_prices_packed_new: DynArray[uint256, MAX_COINS] = last_prices_packed_current - - spot_price: DynArray[uint256, MAX_COINS] = self._get_p(xp, amp, D) - - # -------------------------- Upkeep price oracle ------------------------- - - for i in range(MAX_COINS): - - if i == N_COINS - 1: - break - - if spot_price[i] != 0: - - # Upate packed prices ----------------- - last_prices_packed_new[i] = self.pack_2( - min(spot_price[i], 2 * 10**18), # <----- Cap spot value by 2. - self._calc_moving_average( - last_prices_packed_current[i], - self.ma_exp_time, - ma_last_time_unpacked[0], # index 0 is ma_exp_time for prices - ) - ) - - self.last_prices_packed = last_prices_packed_new - - # ---------------------------- Upkeep D oracle --------------------------- - - last_D_packed_current: uint256 = self.last_D_packed - self.last_D_packed = self.pack_2( - D, - self._calc_moving_average( - last_D_packed_current, - self.D_ma_time, - ma_last_time_unpacked[1], # index 1 is ma_exp_time for D - ) - ) - - # Housekeeping: Update ma_last_time for p and D oracles ------------------ - for i in range(2): - if ma_last_time_unpacked[i] < block.timestamp: - ma_last_time_unpacked[i] = block.timestamp - - self.ma_last_time = self.pack_2(ma_last_time_unpacked[0], ma_last_time_unpacked[1]) - - -@internal -@view -def _calc_moving_average( - packed_value: uint256, - averaging_window: uint256, - ma_last_time: uint256 -) -> uint256: - - last_spot_value: uint256 = packed_value & (2**128 - 1) - last_ema_value: uint256 = (packed_value >> 128) - - if ma_last_time < block.timestamp: # calculate new_ema_value and return that. - alpha: uint256 = self.exp( - -convert( - (block.timestamp - ma_last_time) * 10**18 / averaging_window, int256 - ) - ) - return (last_spot_value * (10**18 - alpha) + last_ema_value * alpha) / 10**18 - - return last_ema_value - - -@view -@external -def last_price(i: uint256) -> uint256: - return self.last_prices_packed[i] & (2**128 - 1) - - -@view -@external -def ema_price(i: uint256) -> uint256: - return (self.last_prices_packed[i] >> 128) - - -@external -@view -def get_p(i: uint256) -> uint256: - """ - @notice Returns the AMM State price of token - @dev if i = 0, it will return the state price of coin[1]. - @param i index of state price (0 for coin[1], 1 for coin[2], ...) - @return uint256 The state price quoted by the AMM for coin[i+1] - """ - amp: uint256 = self._A() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem( - self._stored_rates(), self._balances() - ) - D: uint256 = self.get_D(xp, amp) - return self._get_p(xp, amp, D)[i] - - -@external -@view -@nonreentrant('lock') -def price_oracle(i: uint256) -> uint256: - return self._calc_moving_average( - self.last_prices_packed[i], - self.ma_exp_time, - self.ma_last_time & (2**128 - 1) - ) - - -@external -@view -@nonreentrant('lock') -def D_oracle() -> uint256: - return self._calc_moving_average( - self.last_D_packed, - self.D_ma_time, - self.ma_last_time >> 128 - ) - - -# ----------------------------- Math Utils ----------------------------------- - - -@internal -@pure -def exp(x: int256) -> uint256: - """ - @dev Calculates the natural exponential function of a signed integer with - a precision of 1e18. - @notice Note that this function consumes about 810 gas units. The implementation - is inspired by Remco Bloemen's implementation under the MIT license here: - https://xn--2-umb.com/22/exp-ln. - @dev This implementation is derived from Snekmate, which is authored - by pcaversaccio (Snekmate), distributed under the AGPL-3.0 license. - https://github.com/pcaversaccio/snekmate - @param x The 32-byte variable. - @return int256 The 32-byte calculation result. - """ - value: int256 = x - - # If the result is `< 0.5`, we return zero. This happens when we have the following: - # "x <= floor(log(0.5e18) * 1e18) ~ -42e18". - if (x <= -42139678854452767551): - return empty(uint256) - - # When the result is "> (2 ** 255 - 1) / 1e18" we cannot represent it as a signed integer. - # This happens when "x >= floor(log((2 ** 255 - 1) / 1e18) * 1e18) ~ 135". - assert x < 135305999368893231589, "wad_exp overflow" - - # `x` is now in the range "(-42, 136) * 1e18". Convert to "(-42, 136) * 2 ** 96" for higher - # intermediate precision and a binary base. This base conversion is a multiplication with - # "1e18 / 2 ** 96 = 5 ** 18 / 2 ** 78". - value = unsafe_div(x << 78, 5 ** 18) - - # Reduce the range of `x` to "(-½ ln 2, ½ ln 2) * 2 ** 96" by factoring out powers of two - # so that "exp(x) = exp(x') * 2 ** k", where `k` is a signer integer. Solving this gives - # "k = round(x / log(2))" and "x' = x - k * log(2)". Thus, `k` is in the range "[-61, 195]". - k: int256 = unsafe_add(unsafe_div(value << 96, 54916777467707473351141471128), 2 ** 95) >> 96 - value = unsafe_sub(value, unsafe_mul(k, 54916777467707473351141471128)) - - # Evaluate using a "(6, 7)"-term rational approximation. Since `p` is monic, - # we will multiply by a scaling factor later. - y: int256 = unsafe_add(unsafe_mul(unsafe_add(value, 1346386616545796478920950773328), value) >> 96, 57155421227552351082224309758442) - p: int256 = unsafe_add(unsafe_mul(unsafe_add(unsafe_mul(unsafe_sub(unsafe_add(y, value), 94201549194550492254356042504812), y) >> 96,\ - 28719021644029726153956944680412240), value), 4385272521454847904659076985693276 << 96) - - # We leave `p` in the "2 ** 192" base so that we do not have to scale it up - # again for the division. - q: int256 = unsafe_add(unsafe_mul(unsafe_sub(value, 2855989394907223263936484059900), value) >> 96, 50020603652535783019961831881945) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 533845033583426703283633433725380) - q = unsafe_add(unsafe_mul(q, value) >> 96, 3604857256930695427073651918091429) - q = unsafe_sub(unsafe_mul(q, value) >> 96, 14423608567350463180887372962807573) - q = unsafe_add(unsafe_mul(q, value) >> 96, 26449188498355588339934803723976023) - - # The polynomial `q` has no zeros in the range because all its roots are complex. - # No scaling is required, as `p` is already "2 ** 96" too large. Also, - # `r` is in the range "(0.09, 0.25) * 2**96" after the division. - r: int256 = unsafe_div(p, q) - - # To finalise the calculation, we have to multiply `r` by: - # - the scale factor "s = ~6.031367120", - # - the factor "2 ** k" from the range reduction, and - # - the factor "1e18 / 2 ** 96" for the base conversion. - # We do this all at once, with an intermediate result in "2**213" base, - # so that the final right shift always gives a positive value. - - # Note that to circumvent Vyper's safecast feature for the potentially - # negative parameter value `r`, we first convert `r` to `bytes32` and - # subsequently to `uint256`. Remember that the EVM default behaviour is - # to use two's complement representation to handle signed integers. - return unsafe_mul(convert(convert(r, bytes32), uint256), 3822833074963236453042738258902158003155416615667) >> convert(unsafe_sub(195, k), uint256) - - -# ---------------------------- ERC20 Utils ----------------------------------- - -@view -@internal -def _domain_separator() -> bytes32: - if chain.id != CACHED_CHAIN_ID: - return keccak256( - _abi_encode( - EIP712_TYPEHASH, - NAME_HASH, - VERSION_HASH, - chain.id, - self, - salt, - ) - ) - return CACHED_DOMAIN_SEPARATOR - - -@internal -def _transfer(_from: address, _to: address, _value: uint256): - # # NOTE: vyper does not allow underflows - # # so the following subtraction would revert on insufficient balance - self.balanceOf[_from] -= _value - self.balanceOf[_to] += _value - - log Transfer(_from, _to, _value) - - -@internal -def _burnFrom(_from: address, _burn_amount: uint256): - - self.total_supply -= _burn_amount - self.balanceOf[_from] -= _burn_amount - log Transfer(_from, empty(address), _burn_amount) - - -@external -def transfer(_to : address, _value : uint256) -> bool: - """ - @dev Transfer token for a specified address - @param _to The address to transfer to. - @param _value The amount to be transferred. - """ - self._transfer(msg.sender, _to, _value) - return True - - -@external -def transferFrom(_from : address, _to : address, _value : uint256) -> bool: - """ - @dev Transfer tokens from one address to another. - @param _from address The address which you want to send tokens from - @param _to address The address which you want to transfer to - @param _value uint256 the amount of tokens to be transferred - """ - self._transfer(_from, _to, _value) - - _allowance: uint256 = self.allowance[_from][msg.sender] - if _allowance != max_value(uint256): - self.allowance[_from][msg.sender] = _allowance - _value - - return True - - -@external -def approve(_spender : address, _value : uint256) -> bool: - """ - @notice Approve the passed address to transfer the specified amount of - tokens on behalf of msg.sender - @dev Beware that changing an allowance via this method brings the risk that - someone may use both the old and new allowance by unfortunate transaction - ordering: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - @param _spender The address which will transfer the funds - @param _value The amount of tokens that may be transferred - @return bool success - """ - self.allowance[msg.sender][_spender] = _value - - log Approval(msg.sender, _spender, _value) - return True - - -@external -def permit( - _owner: address, - _spender: address, - _value: uint256, - _deadline: uint256, - _v: uint8, - _r: bytes32, - _s: bytes32 -) -> bool: - """ - @notice Approves spender by owner's signature to expend owner's tokens. - See https://eips.ethereum.org/EIPS/eip-2612. - @dev Inspired by https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L753-L793 - @dev Supports smart contract wallets which implement ERC1271 - https://eips.ethereum.org/EIPS/eip-1271 - @param _owner The address which is a source of funds and has signed the Permit. - @param _spender The address which is allowed to spend the funds. - @param _value The amount of tokens to be spent. - @param _deadline The timestamp after which the Permit is no longer valid. - @param _v The bytes[64] of the valid secp256k1 signature of permit by owner - @param _r The bytes[0:32] of the valid secp256k1 signature of permit by owner - @param _s The bytes[32:64] of the valid secp256k1 signature of permit by owner - @return True, if transaction completes successfully - """ - assert _owner != empty(address) - assert block.timestamp <= _deadline - - nonce: uint256 = self.nonces[_owner] - digest: bytes32 = keccak256( - concat( - b"\x19\x01", - self._domain_separator(), - keccak256(_abi_encode(EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline)) - ) - ) - - if _owner.is_contract: - sig: Bytes[65] = concat(_abi_encode(_r, _s), slice(convert(_v, bytes32), 31, 1)) - # reentrancy not a concern since this is a staticcall - assert ERC1271(_owner).isValidSignature(digest, sig) == ERC1271_MAGIC_VAL - else: - assert ecrecover(digest, convert(_v, uint256), convert(_r, uint256), convert(_s, uint256)) == _owner - - self.allowance[_owner][_spender] = _value - self.nonces[_owner] = nonce + 1 - - log Approval(_owner, _spender, _value) - return True - - -@view -@external -def DOMAIN_SEPARATOR() -> bytes32: - """ - @notice EIP712 domain separator. - @return bytes32 Domain Separator set for the current chain. - """ - return self._domain_separator() - - -# ------------------------- AMM View Functions ------------------------------- - - -@view -@external -def get_dx(i: int128, j: int128, dy: uint256) -> uint256: - """ - @notice Calculate the current input dx given output dy - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dy Amount of `j` being received after exchange - @return Amount of `i` predicted - """ - return StableSwapViews(factory.views_implementation()).get_dx(i, j, dy, self) - - -@view -@external -def get_dy(i: int128, j: int128, dx: uint256) -> uint256: - """ - @notice Calculate the current output dy given input dx - @dev Index values can be found via the `coins` public getter method - @param i Index value for the coin to send - @param j Index valie of the coin to recieve - @param dx Amount of `i` being exchanged - @return Amount of `j` predicted - """ - return StableSwapViews(factory.views_implementation()).get_dy(i, j, dx, self) - - -@view -@external -def calc_withdraw_one_coin(_burn_amount: uint256, i: int128) -> uint256: - """ - @notice Calculate the amount received when withdrawing a single coin - @param _burn_amount Amount of LP tokens to burn in the withdrawal - @param i Index value of the coin to withdraw - @return Amount of coin received - """ - return self._calc_withdraw_one_coin(_burn_amount, i)[0] - - -@view -@external -@nonreentrant('lock') -def totalSupply() -> uint256: - """ - @notice The total supply of pool LP tokens - @return self.total_supply, 18 decimals. - """ - return self.total_supply - - -@view -@external -@nonreentrant('lock') -def get_virtual_price() -> uint256: - """ - @notice The current virtual price of the pool LP token - @dev Useful for calculating profits. - The method may be vulnerable to donation-style attacks if implementation - contains rebasing tokens. For integrators, caution is advised. - @return LP token virtual price normalized to 1e18 - """ - amp: uint256 = self._A() - xp: DynArray[uint256, MAX_COINS] = self._xp_mem( - self._stored_rates(), self._balances() - ) - D: uint256 = self.get_D(xp, amp) - # D is in the units similar to DAI (e.g. converted to precision 1e18) - # When balanced, D = n * x_u - total virtual value of the portfolio - return D * PRECISION / self.total_supply - - -@view -@external -def calc_token_amount( - _amounts: DynArray[uint256, MAX_COINS], - _is_deposit: bool -) -> uint256: - """ - @notice Calculate addition or reduction in token supply from a deposit or withdrawal - @param _amounts Amount of each coin being deposited - @param _is_deposit set True for deposits, False for withdrawals - @return Expected amount of LP tokens received - """ - return StableSwapViews(factory.views_implementation()).calc_token_amount(_amounts, _is_deposit, self) - - -@view -@external -def A() -> uint256: - return self._A() / A_PRECISION - - -@view -@external -def A_precise() -> uint256: - return self._A() - - -@view -@external -def balances(i: uint256) -> uint256: - """ - @notice Get the current balance of a coin within the - pool, less the accrued admin fees - @param i Index value for the coin to query balance of - @return Token balance - """ - return self._balances()[i] - - -@view -@external -def get_balances() -> DynArray[uint256, MAX_COINS]: - return self._balances() - - -@view -@external -def stored_rates() -> DynArray[uint256, MAX_COINS]: - return self._stored_rates() - - -@view -@external -def dynamic_fee(i: int128, j: int128) -> uint256: - """ - @notice Return the fee for swapping between `i` and `j` - @param i Index value for the coin to send - @param j Index value of the coin to recieve - @return Swap fee expressed as an integer with 1e10 precision - """ - return StableSwapViews(factory.views_implementation()).dynamic_fee(i, j, self) - - -# --------------------------- AMM Admin Functions ---------------------------- - - -@external -def set_new_fee(_new_fee: uint256, _new_offpeg_fee_multiplier: uint256): - - assert msg.sender == factory.admin() - - # set new fee: - assert _new_fee <= MAX_FEE - self.fee = _new_fee - - # set new offpeg_fee_multiplier: - assert _new_offpeg_fee_multiplier * _new_fee <= MAX_FEE * FEE_DENOMINATOR # dev: offpeg multiplier exceeds maximum - self.offpeg_fee_multiplier = _new_offpeg_fee_multiplier - - log ApplyNewFee(_new_fee, _new_offpeg_fee_multiplier) - - -@external -def set_ma_exp_time(_ma_exp_time: uint256, _D_ma_time: uint256): - """ - @notice Set the moving average window of the price oracles. - @param _ma_exp_time Moving average window. It is time_in_seconds / ln(2) - """ - assert msg.sender == factory.admin() # dev: only owner - assert 0 not in [_ma_exp_time, _D_ma_time] - - self.ma_exp_time = _ma_exp_time - self.D_ma_time = _D_ma_time - - -@external -def set_A_oracle(_oracle: address, _method_id: bytes4): - """ - @notice Set the address and method id of the external contract that - governs Amplification Factor - @dev Only settable by the Pool Manager once. - """ - assert POOL_MANAGER == tx.origin # dev: only pool manager - assert self.A_oracle == 0 # dev: A_oracle already set - - self.A_oracle = convert(_method_id, uint256) * 2**224 | convert(_oracle, uint256) - - log SetNewAOracle(_oracle) From c039dc5637d2acb01ca6bd44b8886158fe24115c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:17:59 +0530 Subject: [PATCH 275/337] use immutable boolean for rebasing checks --- contracts/main/CurveStableSwapMetaNG.vy | 8 +++++--- contracts/main/CurveStableSwapNG.vy | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index d3b1e47a..795abdbe 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -215,6 +215,7 @@ math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) asset_type: immutable(uint8) +pool_is_rebasing: immutable(bool) stored_balances: uint256[N_COINS] # Fee specific vars @@ -346,6 +347,7 @@ def __init__( coins = _coins # <---------------- coins[1] is always base pool LP token. asset_type = _asset_types[0] + pool_is_rebasing = asset_type == 2 rate_multiplier = _rate_multipliers[0] for i in range(MAX_COINS): @@ -498,7 +500,7 @@ def _transfer_out( """ assert receiver != empty(address) # dev: do not send tokens to zero_address - if asset_type != 2: + if not pool_is_rebasing: self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( @@ -569,7 +571,7 @@ def _balances() -> uint256[N_COINS]: admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128): - if asset_type != 2: + if pool_is_rebasing: result[i] = ERC20(coins[i]).balanceOf(self) - admin_balances[i] else: result[i] = self.stored_balances[i] - admin_balances[i] @@ -634,7 +636,7 @@ def exchange_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert asset_type != 2 # dev: exchange_received not supported if pool contains rebasing tokens + assert not pool_is_rebasing # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index f4f32c10..cf0342fe 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -159,6 +159,7 @@ PRECISION: constant(uint256) = 10 ** 18 factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) asset_types: immutable(DynArray[uint8, MAX_COINS]) +pool_is_rebasing: immutable(bool) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -274,6 +275,7 @@ def __init__( coins = _coins asset_types = _asset_types + pool_is_rebasing = 2 in asset_types __n_coins: uint256 = len(_coins) N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) @@ -403,7 +405,7 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): """ assert receiver != empty(address) # dev: do not send tokens to zero_address - if not 2 in asset_types: + if not pool_is_rebasing: self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( @@ -478,7 +480,7 @@ def _balances() -> DynArray[uint256, MAX_COINS]: for i in range(N_COINS_128, bound=MAX_COINS_128): - if 2 in asset_types: + if pool_is_rebasing: balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] else: balances_i = self.stored_balances[i] - self.admin_balances[i] @@ -545,7 +547,7 @@ def exchange_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not 2 in asset_types # dev: exchange_received not supported if pool contains rebasing tokens + assert not pool_is_rebasing # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, From 7e0f67e6b69a7e6c673b18ec44296f67ffcea295 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:22:36 +0530 Subject: [PATCH 276/337] add note on asset type usage --- contracts/main/CurveStableSwapMetaNG.vy | 2 ++ contracts/main/CurveStableSwapNG.vy | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 795abdbe..d0e9c27c 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -28,6 +28,8 @@ Note: Some ERC4626 implementations may be susceptible to Donation/Inflation attacks. Users are advised to proceed with caution. + NOTE: Pool Cannot support tokens with multiple asset types: e.g. ERC4626 + with fees are not supported. Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index cf0342fe..7481dd53 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -24,6 +24,8 @@ Note: Some ERC4626 implementations may be susceptible to Donation/Inflation attacks. Users are advised to proceed with caution. + NOTE: Pool Cannot support tokens with multiple asset types: e.g. ERC4626 + with fees are not supported. Supports: 1. ERC20 support for return True/revert, return True/False, return None 2. ERC20 tokens can have arbitrary decimals (<=18). From 8eaf5e2bcf9e47f0b18d0fc737f6bcdd384e63c5 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:25:37 +0530 Subject: [PATCH 277/337] rename var for clarity and add some more codecomments --- contracts/main/CurveStableSwapMetaNG.vy | 14 +++++++++----- contracts/main/CurveStableSwapNG.vy | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index d0e9c27c..62be99e3 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -217,7 +217,7 @@ math: immutable(Math) factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) asset_type: immutable(uint8) -pool_is_rebasing: immutable(bool) +pool_contains_rebasing_tokens: immutable(bool) stored_balances: uint256[N_COINS] # Fee specific vars @@ -349,7 +349,7 @@ def __init__( coins = _coins # <---------------- coins[1] is always base pool LP token. asset_type = _asset_types[0] - pool_is_rebasing = asset_type == 2 + pool_contains_rebasing_tokens = asset_type == 2 rate_multiplier = _rate_multipliers[0] for i in range(MAX_COINS): @@ -502,8 +502,9 @@ def _transfer_out( """ assert receiver != empty(address) # dev: do not send tokens to zero_address - if not pool_is_rebasing: + if not pool_contains_rebasing_tokens: + # we need not cache balanceOf pool before swap out self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True @@ -511,6 +512,7 @@ def _transfer_out( else: + # cache balances pre and post to account for fee on transfers etc. coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True @@ -573,9 +575,11 @@ def _balances() -> uint256[N_COINS]: admin_balances: DynArray[uint256, MAX_COINS] = self.admin_balances for i in range(N_COINS_128): - if pool_is_rebasing: + if pool_contains_rebasing_tokens: + # Read balances by gulping to account for rebases result[i] = ERC20(coins[i]).balanceOf(self) - admin_balances[i] else: + # Use cached balances result[i] = self.stored_balances[i] - admin_balances[i] return result @@ -638,7 +642,7 @@ def exchange_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not pool_is_rebasing # dev: exchange_received not supported if pool contains rebasing tokens + assert not pool_contains_rebasing_tokens # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 7481dd53..f192932a 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -161,7 +161,7 @@ PRECISION: constant(uint256) = 10 ** 18 factory: immutable(Factory) coins: public(immutable(DynArray[address, MAX_COINS])) asset_types: immutable(DynArray[uint8, MAX_COINS]) -pool_is_rebasing: immutable(bool) +pool_contains_rebasing_tokens: immutable(bool) stored_balances: DynArray[uint256, MAX_COINS] # Fee specific vars @@ -277,7 +277,7 @@ def __init__( coins = _coins asset_types = _asset_types - pool_is_rebasing = 2 in asset_types + pool_contains_rebasing_tokens = 2 in asset_types __n_coins: uint256 = len(_coins) N_COINS = __n_coins N_COINS_128 = convert(__n_coins, int128) @@ -407,8 +407,9 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): """ assert receiver != empty(address) # dev: do not send tokens to zero_address - if not pool_is_rebasing: + if not pool_contains_rebasing_tokens: + # we need not cache balanceOf pool before swap out self.stored_balances[_coin_idx] -= _amount assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True @@ -416,6 +417,7 @@ def _transfer_out(_coin_idx: int128, _amount: uint256, receiver: address): else: + # cache balances pre and post to account for fee on transfers etc. coin_balance: uint256 = ERC20(coins[_coin_idx]).balanceOf(self) assert ERC20(coins[_coin_idx]).transfer( receiver, _amount, default_return_value=True @@ -482,9 +484,11 @@ def _balances() -> DynArray[uint256, MAX_COINS]: for i in range(N_COINS_128, bound=MAX_COINS_128): - if pool_is_rebasing: + if pool_contains_rebasing_tokens: + # Read balances by gulping to account for rebases balances_i = ERC20(coins[i]).balanceOf(self) - self.admin_balances[i] else: + # Use cached balances balances_i = self.stored_balances[i] - self.admin_balances[i] result.append(balances_i) @@ -549,7 +553,7 @@ def exchange_received( @param _receiver Address that receives `j` @return Actual amount of `j` received """ - assert not pool_is_rebasing # dev: exchange_received not supported if pool contains rebasing tokens + assert not pool_contains_rebasing_tokens # dev: exchange_received not supported if pool contains rebasing tokens return self._exchange( msg.sender, i, From b3fc89a65f4da95e02079ec4b57befcf80fa561c Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:13:57 +0530 Subject: [PATCH 278/337] deployments --- scripts/deploy_infra.py | 163 ++++++++++++++++------------------ scripts/deploy_proxy_admin.py | 14 +-- scripts/deployment_utils.py | 48 +--------- 3 files changed, 88 insertions(+), 137 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index b98b77c3..be0c694c 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -12,135 +12,128 @@ deployments = { # Ethereum - "ethereum:sepolia": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", - "gauge": "", - }, "ethereum:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xc9CBC565A9F4120a2740ec6f64CC24AeB2bB3E5E", + "views": "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA", + "plain_amm": "0xDCc91f930b42619377C200BA05b7513f2958b202", + "meta_amm": "0xede71F77d7c900dCA5892720E76316C6E575F0F7", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "gauge": "", + "gauge": "0x38D9BdA812da2C68dFC6aDE85A7F7a54E77F8325", + }, + "ethereum:sepolia": { + "math": "0x2cad7b3e78e10bcbf2cc443ddd69ca8bcc09a758", + "views": "0x9d3975070768580f755D405527862ee126d0eA08", + "plain_amm": "0xE12374F193f91f71CE40D53E0db102eBaA9098D5", + "meta_amm": "0xB00E89EaBD59cD3254c88E390103Cf17E914f678", + "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", }, # Layer 2 "arbitrum:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xD4a8bd4d59d65869E99f20b642023a5015619B34", + "views": "0x9293f068912bae932843a1bA01806c54f416019D", + "plain_amm": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", + "meta_amm": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", }, "optimism:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "views": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "plain_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "meta_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "base:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, "linea:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "plain_amm": "0xa7b9d886a9a374a1c86dc52d2ba585c5cdfdac26", + "meta_amm": "0xf3a6aa40cf048a3960e9664847e9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, "scroll:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, - "zksync:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", - }, "pzkevm:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", }, # Layer 1 "gnosis:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xFAbC421e3368D158d802684A217a83c083c94CeB", + "views": "0x0c59d36b23f809f8b6C7cb4c8C590a0AC103baEf", + "plain_amm": "0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a", + "meta_amm": "0xC1b393EfEF38140662b91441C6710Aa704973228", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", }, "polygon:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "avax:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "ftm:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "views": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "plain_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", + "meta_amm": "0x046207cB759F527b6c10C2D61DBaca45513685CC", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "bsc:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", + "views": "0x5Ea9DD3b6f042A34Df818C6c1324BC5A7c61427a", + "plain_amm": "0x505d666E4DD174DcDD7FA090ed95554486d2Be44", + "meta_amm": "0x5a8C93EE12a8Df4455BA111647AdA41f29D5CfcC", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", }, "celo:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "kava:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", }, "aurora:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", }, - "tron:mainnet": { - "math": "", - "views": "", - "plain_amm": "", - "meta_amm": "", - "factory": "", + "mantle:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "factory_ctor": "000000000000000000000000f3a431008396df8a8b2df492c913706bdb0874ef0000000000000000000000002d12d0907a388811e3aa855a550f959501d303ee", # noqa:E501 }, } @@ -261,9 +254,9 @@ def deploy_infra(network, url, account, fork=False): def main(): deploy_infra( - ":mainnet", - os.environ["RPC_"], - "", + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], + "FIDDYDEPLOYER", fork=False, ) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index e65e8ae3..ca607c85 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -2,9 +2,9 @@ import sys import boa -import deployment_utils as deploy_utils from boa.network import NetworkEnv from deploy_infra import set_evm_version +from deployment_utils import BABE, FIDDYDEPLOYER from eth_abi import encode from eth_account import Account from rich.console import Console as RichConsole @@ -19,7 +19,7 @@ def deploy_proxy_admin(network, url, account, fork=False): if fork: boa.env.fork(url) logger.log("Forkmode ...") - boa.env.eoa = deploy_utils.FIDDYDEPLOYER + boa.env.eoa = FIDDYDEPLOYER else: logger.log("Prodmode ...") boa.set_env(NetworkEnv(url)) @@ -27,7 +27,7 @@ def deploy_proxy_admin(network, url, account, fork=False): # deploy thin proxy if no owners exist: proxy_admin_contract_obj = set_evm_version("./contracts/ProxyAdmin.vy", network) - args = [deploy_utils.FIDDYDEPLOYER, deploy_utils.BABE] + args = [FIDDYDEPLOYER, BABE] encoded_args = encode(["address", "address"], args).hex() logger.log(f"Constructor: {encoded_args}") proxy_admin = proxy_admin_contract_obj.deploy(args) @@ -36,10 +36,10 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): deploy_proxy_admin( - "mantle:mainnet", - os.environ["RPC_MANTLE"], - "FIDDYDEPLOYER", - fork=True, + ":mainnet", + os.environ["RPC_"], + "", + fork=False, ) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 3570375b..b7af3632 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -1,48 +1,6 @@ from dataclasses import dataclass -import click -from ape import networks, project -from ape.api.address import Address - -# from eth_utils import function_signature_to_4byte_selector - -DOLLAR_VALUE_OF_TOKENS_TO_DEPOSIT = 5 - - -def _get_tx_params(): - - if "mainnet-fork" == networks.active_provider.network.name: - return {} - - if "sepolia" == networks.active_provider.network.name: - return {} - - active_provider = networks.active_provider - max_fee = int(active_provider.base_fee * 1.2) - max_priority_fee = int(0.5e9) - - return {"max_fee": max_fee, "max_priority_fee": max_priority_fee} - - -def deploy_blueprint(contract, account): - - initcode = contract.contract_type.deployment_bytecode.bytecode - if isinstance(initcode, str): - initcode = bytes.fromhex(initcode.removeprefix("0x")) - initcode = b"\xfe\x71\x00" + initcode # eip-5202 preamble version 0 - initcode = b"\x61" + len(initcode).to_bytes(2, "big") + b"\x3d\x81\x60\x0a\x3d\x39\xf3" + initcode - - tx = project.provider.network.ecosystem.create_transaction( - chain_id=project.provider.chain_id, - data=initcode, - gas_price=project.provider.gas_price, - nonce=account.nonce, - **_get_tx_params(), - ) - receipt = account.call(tx) - click.echo(f"blueprint deployed at: {receipt.contract_address}") - return receipt.contract_address - +from eth_typing import Address # ------ IMMUTABLES ------ @@ -145,8 +103,8 @@ class CurveNetworkSettings: fee_receiver_address="", ), "mantle:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), } From c91d58ff82d159c9e68c6647da03aa00a25d0178 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 11 Jan 2024 13:09:09 +0100 Subject: [PATCH 279/337] Remove command line arguments --- README.MD | 10 +--- tests/conftest.py | 121 ++++++++++----------------------------- tests/fixtures/pools.py | 2 - tests/fixtures/tokens.py | 32 +++-------- 4 files changed, 37 insertions(+), 128 deletions(-) diff --git a/README.MD b/README.MD index bb547c48..20344085 100644 --- a/README.MD +++ b/README.MD @@ -13,7 +13,7 @@ For a full list of deployments, please check: [The deployment script](scripts/de The metapool factory has several core components: - [`Factory`](contracts/main/CurveStableSwapFactoryNG.vy) is the main contract used to deploy new metapools. It also acts a registry for finding the deployed pools and querying information about them. -- New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwapNG.vy) targetted by the proxy is determined according to the base pool. +- New pools are deployed via blueprints. The [implementation contract](contracts/main/CurveStableSwapNG.vy) targeted by the proxy is determined according to the base pool. See the [documentation](https://docs.curve.fi) for more detailed information. @@ -28,14 +28,6 @@ pip install poetry==1.5.1 poetry install ``` -### Paramaters - -- `--pool-size` - size of pool (N_COINS), available parameters: `[2]` -- `--pool-type` - type of pool, available parameters: `[basic,meta]` -- `--token-types` - token types to test against(divided by comma), available parameters: `[plain,eth,oracle,rebasing]` -- `--decimals` - token decimals (divided by comma), default `18,18` -- `--return-types` - types of .transfer() returns to test against (divided by comma), default `revert,False,None` - ### Type of tests Testing gauge diff --git a/tests/conftest.py b/tests/conftest.py index ec03b6fd..ce7a4fb3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,148 +18,85 @@ pool_types = {"basic": 0, "meta": 1} token_types = {"plain": 0, "oracle": 1, "rebasing": 2} return_types = {"revert": 0, "False": 1, "None": 2} - - -def pytest_addoption(parser): - parser.addoption( - "--pool-size", - action="store", - default="2", - help="pool size to test against", - ) - parser.addoption( - "--pool-types", - action="store", - default="basic,meta", - help="pool type to test against", - ) - parser.addoption( - "--token-types", - action="store", - default="plain,oracle,rebasing", - help="comma-separated list of ERC20 token types to test against", - ) - parser.addoption( - "--decimals", - action="store", - default="18,18", - help="comma-separated list of ERC20 token precisions to test against", - ) - parser.addoption( - "--return-type", - action="store", - default="revert,False,None", - help="comma-separated list of ERC20 token return types to test against", - ) +decimal_types = [(18, 18), (10, 12)] def pytest_generate_tests(metafunc): - pool_size = int(metafunc.config.getoption("pool_size")) - - if "pool_size" in metafunc.fixturenames: - metafunc.parametrize( - "pool_size", - [pool_size], - indirect=True, - ids=[f"(PoolSize={pool_size})"], - ) - if "pool_type" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("pool_types").split(",") + pool_type_items = sorted(pool_types.items()) metafunc.parametrize( "pool_type", - [pool_types[pool_type] for pool_type in cli_options], - indirect=True, - ids=[f"(PoolType={pool_type})" for pool_type in cli_options], + [v for k, v in pool_type_items], + ids=[f"(PoolType={k})" for k, v in pool_type_items], + indirect=True, # to declare the fixture scope ) if "pool_token_types" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("token_types").split(",") - if "eth" in cli_options: - cli_options.remove("eth") - cli_options = ["eth"] + cli_options - - combinations = list(itertools.combinations_with_replacement(cli_options, pool_size)) - + combinations = sorted(itertools.combinations_with_replacement(token_types.items(), 2)) metafunc.parametrize( "pool_token_types", - [[token_types[idx] for idx in c] for c in combinations], - indirect=True, - ids=[f"(PoolTokenTypes={c})" for c in combinations], + [(v1, v2) for (k1, v1), (k2, v2) in combinations], + ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in combinations], + indirect=True, # to declare the fixture scope ) if "metapool_token_type" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("token_types").split(",") - # for meta pool only 1st coin is selected + token_type_items = sorted(token_types.items()) metafunc.parametrize( "metapool_token_type", - [token_types[c] for c in cli_options], - indirect=True, - ids=[f"(MetaTokenType={c})" for c in cli_options], + [v for k, v in token_type_items], + ids=[f"(MetaTokenType={k})" for k, v in token_type_items], + indirect=True, # to declare the fixture scope ) if "initial_decimals" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("decimals") metafunc.parametrize( "initial_decimals", - [[int(i) for i in cli_options.split(",")]], - indirect=True, - ids=[f"(Decimals={cli_options})"], - ) - - if "return_type" in metafunc.fixturenames: - cli_options = metafunc.config.getoption("return_type").split(",") - return_type_ids = [return_types[v] for v in cli_options] - - metafunc.parametrize( - "return_type", - return_type_ids, - indirect=True, - ids=[f"(ReturnType={i})" for i in cli_options], + decimal_types, + ids=[f"(Decimals={i},{j})" for i, j in decimal_types], + indirect=True, # to declare the fixture scope ) @pytest.fixture(scope="session") -def pool_size(request): - return request.param +def pool_size(): + return 2 @pytest.fixture(scope="session") -def pool_type(request): +def pool_type(request): # to declare the fixture scope return request.param @pytest.fixture(scope="session") -def pool_token_types(request): +def pool_token_types(request): # to declare the fixture scope return request.param @pytest.fixture(scope="session") -def metapool_token_type(request): +def metapool_token_type(request): # to declare the fixture scope return request.param @pytest.fixture(scope="session") -def return_type(request): - return request.param - - -@pytest.fixture(scope="session") -def initial_decimals(request): +def initial_decimals(request): # to declare the fixture scope return request.param @pytest.fixture(scope="session") def decimals(initial_decimals, pool_token_types): - # oracle tokens are always 18 decimals - return [d if t != 1 else 18 for d, t in zip(initial_decimals, pool_token_types)] + return [ + # oracle tokens are always 18 decimals + 18 if token_type == 1 else decimals + for decimals, token_type in zip(initial_decimals, pool_token_types) + ] @pytest.fixture(scope="session") -def meta_decimals(initial_decimals, metapool_token_type, decimals): +def meta_decimals(metapool_token_type, decimals): # oracle tokens are always 18 decimals - return decimals[0] if metapool_token_type != 1 else 18 + return 18 if metapool_token_type == 1 else decimals[0] # Usage diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 2ec0edc6..c7632718 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -9,8 +9,6 @@ def swap( factory, pool_size, pool_type, - pool_token_types, - metapool_token_type, pool_tokens, zero_address, amm_interface, diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 2a98324b..57bbcb49 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -47,31 +47,14 @@ def rebase_tokens(deployer, decimals): @pytest.fixture(scope="module") def pool_tokens(pool_token_types, plain_tokens, oracle_tokens, rebase_tokens): - pool_tokens = [] - for i, t in enumerate(pool_token_types): - if t == 0: - pool_tokens.append(plain_tokens[i]) - elif t == 1: - pool_tokens.append(oracle_tokens[i]) - elif t == 2: - pool_tokens.append(rebase_tokens[i]) - else: - raise ValueError("Wrong pool token type") - - return pool_tokens + tokens = {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens} + return [tokens[t][i] for i, t in enumerate(pool_token_types)] # <--------------------- Metapool configuration ---------------------> @pytest.fixture(scope="module") def metapool_token(metapool_token_type, plain_tokens, oracle_tokens, rebase_tokens): - if metapool_token_type == 0: - return plain_tokens[0] - elif metapool_token_type == 1: - return oracle_tokens[0] - elif metapool_token_type == 2: - return rebase_tokens[0] - else: - raise ValueError("Wrong pool token type") + return {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens}[metapool_token_type][0] @pytest.fixture(scope="module") @@ -81,12 +64,11 @@ def base_pool_decimals(): @pytest.fixture(scope="module") def base_pool_tokens(deployer, base_pool_decimals): - tokens = [] with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20.vy", "DAI", "DAI", base_pool_decimals[0])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDC", "USDC", base_pool_decimals[1])) - tokens.append(boa.load("contracts/mocks/ERC20.vy", "USDT", "USDT", base_pool_decimals[2])) - return tokens + return [ + boa.load("contracts/mocks/ERC20.vy", c, c, base_pool_decimals[i]) + for i, c in enumerate(("DAI", "USDC", "USDT")) + ] @pytest.fixture(scope="module") From 8cd984a468152c64016981d6f6b08e06f4092074 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 11 Jan 2024 13:09:35 +0100 Subject: [PATCH 280/337] Merge CI scripts --- .github/tentative_workflows/test_factory.yaml | 49 -------------- .github/tentative_workflows/test_gauge.yaml | 44 ------------- .github/tentative_workflows/test_pools_2.yaml | 41 ------------ .github/tentative_workflows/test_token.yaml | 35 ---------- .github/workflows/ci.yaml | 66 +++++++++++++++++++ .github/workflows/lint.yaml | 18 ----- 6 files changed, 66 insertions(+), 187 deletions(-) delete mode 100644 .github/tentative_workflows/test_factory.yaml delete mode 100644 .github/tentative_workflows/test_gauge.yaml delete mode 100644 .github/tentative_workflows/test_pools_2.yaml delete mode 100644 .github/tentative_workflows/test_token.yaml create mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/lint.yaml diff --git a/.github/tentative_workflows/test_factory.yaml b/.github/tentative_workflows/test_factory.yaml deleted file mode 100644 index ed7eca94..00000000 --- a/.github/tentative_workflows/test_factory.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: test_factory - -on: - pull_request: - paths: - - "tests/test_factory.py" - - "contracts/CurveStableSwapFactoryNG.vy" - push: - paths: - - "tests/test_factory.py" - - "contracts/CurveStableSwapFactoryNG.vy" - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Cache Compiler Installations - uses: actions/cache@v3 - with: - path: | - ~/.vvm - key: compiler-cache - - - name: Setup Python 3.10.4 - uses: actions/setup-python@v4 - with: - python-version: 3.10.4 - - - name: Install Requirements - run: | - pip install poetry==1.5.1 - poetry config virtualenvs.in-project true - poetry install --no-interaction --without dev - - - name: Run Tests Basic - run: | - source .venv/bin/activate - pytest tests/test_factory.py --token-types=plain -n auto - - - name: Run Tests Forked - run: | - source .venv/bin/activate - pytest tests/test_factory_forked.py --token-types=plain -n auto diff --git a/.github/tentative_workflows/test_gauge.yaml b/.github/tentative_workflows/test_gauge.yaml deleted file mode 100644 index fbb3f03c..00000000 --- a/.github/tentative_workflows/test_gauge.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: test_gauge - -on: - pull_request: - paths: - - "tests/gauge/*.py" - - "contracts/LiquidityGauge.vy" - push: - paths: - - "tests/gauge/*.py" - - "contracts/LiquidityGauge.vy" - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Cache Compiler Installations - uses: actions/cache@v3 - with: - path: | - ~/.vvm - key: compiler-cache - - - name: Setup Python 3.10.4 - uses: actions/setup-python@v4 - with: - python-version: 3.10.4 - - - name: Install Requirements - run: | - pip install poetry==1.5.1 - poetry config virtualenvs.in-project true - poetry install --no-interaction --without dev - - - name: Run Tests - run: | - source .venv/bin/activate - pytest tests/gauge/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,18 -n auto diff --git a/.github/tentative_workflows/test_pools_2.yaml b/.github/tentative_workflows/test_pools_2.yaml deleted file mode 100644 index ca96f01b..00000000 --- a/.github/tentative_workflows/test_pools_2.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: test_pools_2 - -on: [pull_request, push] - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Cache Compiler Installations - uses: actions/cache@v3 - with: - path: | - ~/.vvm - key: compiler-cache - - - name: Setup Python 3.10.4 - uses: actions/setup-python@v4 - with: - python-version: 3.10.4 - - - name: Install Requirements - run: | - pip install poetry==1.5.1 - poetry config virtualenvs.in-project true - poetry install --no-interaction --without dev - - - name: Run All Token Tests 18,18 - run: | - source .venv/bin/activate - pytest tests/pools/ --pool-size=2 -n auto - - - name: Run Plain Tests 18,6 - run: | - source .venv/bin/activate - pytest tests/pools/ --pool-size=2 --pool-type=basic --token-types=plain --decimals=18,6 -n auto diff --git a/.github/tentative_workflows/test_token.yaml b/.github/tentative_workflows/test_token.yaml deleted file mode 100644 index 49bd91fe..00000000 --- a/.github/tentative_workflows/test_token.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: test_token - -on: [pull_request, push] - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Cache Compiler Installations - uses: actions/cache@v3 - with: - path: | - ~/.vvm - key: compiler-cache - - - name: Setup Python 3.10.4 - uses: actions/setup-python@v4 - with: - python-version: 3.10.4 - - - name: Install Requirements - run: | - pip install poetry==1.5.1 - poetry config virtualenvs.in-project true - poetry install --no-interaction --without dev - - - name: Run Tests - run: | - source .venv/bin/activate - pytest tests/test_token.py --token-types=plain -n auto diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..3e20029d --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,66 @@ +name: lint + +on: [push] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + + - uses: pre-commit/action@v3.0.0 + + tests: + runs-on: ubuntu-latest + strategy: + matrix: + name: + - gauge + - pools + - factory + include: + - name: gauge + tests: tests/gauge/ + - name: pools + tests: tests/pools/ + - name: factory + tests: >- + tests/test_factory.py + tests/test_factory_forked.py + tests/test_get_D.py + tests/test_token.py + + steps: + - uses: actions/checkout@v3 + + - name: Cache Compiler Installations + uses: actions/cache@v3 + with: + path: ~/.vvm + key: compiler-cache + + - name: Setup Python 3.10.4 + uses: actions/setup-python@v4 + with: + python-version: 3.10.4 + + - name: Install Requirements + run: | + pip install poetry==1.5.1 + poetry config virtualenvs.in-project true + poetry install --no-interaction --without dev + + - name: Run Tests + run: | + source .venv/bin/activate + pytest -n auto ${{ matrix.tests }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index a0d59e2c..00000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: lint - -on: [pull_request, push] - -jobs: - - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup Python 3.10.4 - uses: actions/setup-python@v4 - with: - python-version: 3.10.4 - - - uses: pre-commit/action@v3.0.0 From 703728969a821abf7d6f60cac5bece12d693ccb9 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 11 Jan 2024 14:21:49 +0100 Subject: [PATCH 281/337] Add secret, naming --- .github/workflows/ci.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3e20029d..4a23f6fb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: lint +name: CI on: [push] @@ -23,6 +23,7 @@ jobs: tests: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: name: - gauge @@ -61,6 +62,8 @@ jobs: poetry install --no-interaction --without dev - name: Run Tests + env: + WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} run: | source .venv/bin/activate pytest -n auto ${{ matrix.tests }} From 9fe7f96c92d1fe1c1baf97164a22dfe0cbe878af Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 11 Jan 2024 16:22:03 +0100 Subject: [PATCH 282/337] Split factory tests --- .github/workflows/ci.yaml | 5 +- README.MD | 2 +- tests/factory/__init__.py | 0 tests/factory/test_factory_add_pools.py | 127 +++++++++++ tests/{ => factory}/test_factory_forked.py | 1 - tests/factory/test_factory_general.py | 15 ++ tests/factory/test_factory_meta.py | 72 ++++++ tests/factory/test_factory_plain.py | 61 +++++ tests/test_factory.py | 252 --------------------- 9 files changed, 279 insertions(+), 256 deletions(-) create mode 100644 tests/factory/__init__.py create mode 100644 tests/factory/test_factory_add_pools.py rename tests/{ => factory}/test_factory_forked.py (95%) create mode 100644 tests/factory/test_factory_general.py create mode 100644 tests/factory/test_factory_meta.py create mode 100644 tests/factory/test_factory_plain.py delete mode 100644 tests/test_factory.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4a23f6fb..2df74611 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,6 +22,7 @@ jobs: tests: runs-on: ubuntu-latest + timeout-minutes: 120 strategy: fail-fast: false matrix: @@ -35,9 +36,9 @@ jobs: - name: pools tests: tests/pools/ - name: factory + tests: tests/factory/ + - name: other tests: >- - tests/test_factory.py - tests/test_factory_forked.py tests/test_get_D.py tests/test_token.py diff --git a/README.MD b/README.MD index 20344085..4d7ee39d 100644 --- a/README.MD +++ b/README.MD @@ -39,7 +39,7 @@ pytest tests/gauge/ Testing factory ```shell -pytest tests/test_factory.py +pytest tests/factory/ ``` Testing swap is ERC20 diff --git a/tests/factory/__init__.py b/tests/factory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/factory/test_factory_add_pools.py b/tests/factory/test_factory_add_pools.py new file mode 100644 index 00000000..47720686 --- /dev/null +++ b/tests/factory/test_factory_add_pools.py @@ -0,0 +1,127 @@ +import boa +import pytest + + +@pytest.fixture +def empty_factory(deployer, fee_receiver, owner): + with boa.env.prank(deployer): + _factory = boa.load( + "contracts/main/CurveStableSwapFactoryNG.vy", + fee_receiver, + owner, + ) + return _factory + + +@pytest.fixture +def empty_factory_with_implementations( + empty_factory, + owner, + gauge_implementation, + views_implementation, + math_implementation, + amm_implementation, + amm_implementation_meta, +): + with boa.env.prank(owner): + empty_factory.set_gauge_implementation(gauge_implementation.address) + empty_factory.set_views_implementation(views_implementation.address) + empty_factory.set_math_implementation(math_implementation.address) + + empty_factory.set_pool_implementations(0, amm_implementation.address) + empty_factory.set_metapool_implementations(0, amm_implementation_meta.address) + + return empty_factory + + +def test_add_base_pool_already_exists( + owner, + factory, + add_base_pool, + base_pool, + base_pool_lp_token, + base_pool_tokens, +): + with boa.reverts(): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + [0] * len(base_pool_tokens), + len(base_pool_tokens), + sender=owner, + ) + + +def test_add_base_pool_only_admin( + factory, + bob, + base_pool, + base_pool_lp_token, + base_pool_tokens, +): + with boa.reverts(): + factory.add_base_pool( + base_pool.address, + base_pool_lp_token.address, + [0] * len(base_pool_tokens), + len(base_pool_tokens), + sender=bob, + ) + + +def test_deploy_plain_pool( + empty_factory_with_implementations, amm_interface, pool_tokens, pool_size, zero_address +): + swap_address = empty_factory_with_implementations.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + 2000, + 1000000, + 20000000000, + 866, + 0, + [0] * pool_size, + [bytes(b"")] * pool_size, + [zero_address] * pool_size, + ) + assert swap_address != zero_address + + swap = amm_interface.at(swap_address) + assert swap.coins(0) == pool_tokens[0].address + assert swap.coins(1) == pool_tokens[1].address + + assert swap.A() == 2000 + assert swap.fee() == 1000000 + + assert empty_factory_with_implementations.pool_count() == 1 + assert empty_factory_with_implementations.pool_list(0) == swap.address + assert empty_factory_with_implementations.get_decimals(swap) == [t.decimals() for t in pool_tokens] + + +def test_pool_count( + empty_factory_with_implementations, + swap, + add_base_pool, + amm_interface, + set_pool_implementations, + pool_tokens, + pool_size, + zero_address, +): + assert empty_factory_with_implementations.pool_count() == 0 + + empty_factory_with_implementations.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + 2000, + 1000000, + 20000000000, + 866, + 0, + [0] * pool_size, + [bytes(b"")] * pool_size, + [zero_address] * pool_size, + ) + assert empty_factory_with_implementations.pool_count() == 1 diff --git a/tests/test_factory_forked.py b/tests/factory/test_factory_forked.py similarity index 95% rename from tests/test_factory_forked.py rename to tests/factory/test_factory_forked.py index a624525d..1111de83 100644 --- a/tests/test_factory_forked.py +++ b/tests/factory/test_factory_forked.py @@ -13,7 +13,6 @@ def empty_factory(deployer, fee_receiver, owner): return _factory -@pytest.mark.only_for_pool_type(1) def test_add_base_pool(empty_factory, owner, forked_chain): fraxusdc = "0xdcef968d416a41cdac0ed8702fac8128a64241a2" lp_token = "0x3175df0976dfa876431c2e9ee6bc45b65d3473cc" diff --git a/tests/factory/test_factory_general.py b/tests/factory/test_factory_general.py new file mode 100644 index 00000000..45e96cd8 --- /dev/null +++ b/tests/factory/test_factory_general.py @@ -0,0 +1,15 @@ +def test_get_A(factory, swap): + assert factory.get_A(swap.address) == swap.A() + + +def test_get_fees(factory, swap): + assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) + + +def test_get_admin_balances(factory, swap, pool_size): + balances = [swap.admin_balances(i) for i in range(pool_size)] + assert factory.get_admin_balances(swap.address) == balances + + +def test_fee_receiver(factory, fee_receiver): + assert factory.fee_receiver() == fee_receiver diff --git a/tests/factory/test_factory_meta.py b/tests/factory/test_factory_meta.py new file mode 100644 index 00000000..819c5539 --- /dev/null +++ b/tests/factory/test_factory_meta.py @@ -0,0 +1,72 @@ +import itertools + +import boa +import pytest + +MAX_COINS = 8 + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_find_pool_for_coins(factory, meta_swap, underlying_tokens, sending, receiving): + assert ( + factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) + == meta_swap.address + ) + + +@pytest.mark.parametrize("idx", range(1, 4)) +def test_find_pool_for_coins_underlying(factory, meta_swap, underlying_tokens, idx): + assert factory.find_pool_for_coins(underlying_tokens[0], underlying_tokens[idx]) == meta_swap.address + assert factory.find_pool_for_coins(underlying_tokens[idx], underlying_tokens[0]) == meta_swap.address + + +def test_get_meta_n_coins(factory, meta_swap): + assert factory.get_meta_n_coins(meta_swap.address) == (2, 4) + + +def test_get_underlying_coins(factory, meta_swap, underlying_tokens): + tokens = [underlying_tokens[0]] + underlying_tokens[2:] + assert factory.get_underlying_coins(meta_swap.address) == [t.address for t in tokens] + + +def test_get_underlying_decimals(factory, meta_swap, base_pool_decimals, pool_type): + assert factory.get_underlying_decimals(meta_swap.address) == [18] + base_pool_decimals + + +def test_get_metapool_rates(factory, meta_swap, base_pool, initial_setup): + assert factory.get_metapool_rates(meta_swap.address) == [10 ** 18, base_pool.get_virtual_price()] + + +def test_get_underlying_balances(factory, meta_swap, base_pool, initial_setup): + assert factory.get_metapool_rates(meta_swap.address) == [10 ** 18, base_pool.get_virtual_price()] + + +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) +def test_find_pool_underlying_base_pool_only( + self, factory, underlying_tokens, sending, receiving, zero_address +): + assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address + + +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(2, 5), 2)) +def test_get_coin_indices_underlying(factory, meta_swap, sending, receiving, underlying_tokens): + i, j, is_underlying = factory.get_coin_indices( + meta_swap, underlying_tokens[sending], underlying_tokens[receiving] + ) + assert i == sending - 1 + assert j == receiving - 1 + assert is_underlying is True + + +@pytest.mark.parametrize("idx", range(1, 4)) +def test_get_coin_indices_reverts(factory, meta_swap, base_pool_lp_token, underlying_tokens, idx): + with boa.reverts(): + factory.get_coin_indices(meta_swap.address, base_pool_lp_token.address, underlying_tokens[idx]) + + +def test_get_implementation_address(factory, meta_swap, amm_implementation_meta): + assert factory.get_implementation_address(meta_swap.address) == amm_implementation_meta.address + + +def test_is_meta(factory, meta_swap): + assert factory.is_meta(meta_swap.address) is True diff --git a/tests/factory/test_factory_plain.py b/tests/factory/test_factory_plain.py new file mode 100644 index 00000000..98a4c213 --- /dev/null +++ b/tests/factory/test_factory_plain.py @@ -0,0 +1,61 @@ +import boa +import pytest + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_find_pool_for_coins(factory, basic_swap, plain_tokens, sending, receiving): + assert ( + factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) + == basic_swap.address + ) + + +def test_get_n_coins(factory, swap, plain_tokens, pool_size): + assert factory.get_n_coins(swap.address) == 2 + + +def test_get_coins(factory, swap, plain_tokens, pool_size): + assert factory.get_coins(swap.address) == [pt.address for pt in plain_tokens] + + +def test_get_decimals(factory, swap, decimals): + assert factory.get_decimals(swap.address) == decimals + + +def test_get_balances(factory, swap, pool_size): + assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] + + +def test_get_underlying_balances(factory, basic_swap): + with boa.reverts() as e: + factory.get_underlying_balances(basic_swap.address) + assert str(e) == "dev: pool is not a metapool" + + +def test_get_A(factory, swap): + assert factory.get_A(swap.address) == swap.A() + + +def test_get_fees(factory, swap): + assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_get_coin_indices(factory, swap, sending, receiving, plain_tokens): + i, j, is_underlying = factory.get_coin_indices( + swap.address, plain_tokens[sending].address, plain_tokens[receiving].address + ) + assert i == sending + assert j == receiving + + +def test_get_implementation_address(factory, swap, amm_implementation): + assert factory.get_implementation_address(swap.address) == amm_implementation.address + + +def test_is_meta(factory, swap): + assert factory.is_meta(swap.address) is False + + +def test_get_pool_types(factory, swap, pool_token_types): + assert factory.get_pool_asset_types(swap.address) == list(pool_token_types) diff --git a/tests/test_factory.py b/tests/test_factory.py deleted file mode 100644 index e0f3cacc..00000000 --- a/tests/test_factory.py +++ /dev/null @@ -1,252 +0,0 @@ -import itertools - -import boa -import pytest - -MAX_COINS = 8 - - -class TestFactory: - class TestGeneral: - def test_get_A(self, factory, swap): - assert factory.get_A(swap.address) == swap.A() - - def test_get_fees(self, factory, swap): - assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) - - def test_get_admin_balances(self, factory, swap, pool_size): - balances = [swap.admin_balances(i) for i in range(pool_size)] - assert factory.get_admin_balances(swap.address) == balances - - def test_fee_receiver(self, factory, fee_receiver): - assert factory.fee_receiver() == fee_receiver - - @pytest.mark.only_for_pool_type(0) - class TestBasic: - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_find_pool_for_coins(self, factory, swap, pool_tokens, sending, receiving): - assert ( - factory.find_pool_for_coins(pool_tokens[sending].address, pool_tokens[receiving].address) - == swap.address - ) - - def test_get_n_coins(self, factory, swap, pool_tokens, pool_size): - assert factory.get_n_coins(swap.address) == 2 - - def test_get_coins(self, factory, swap, pool_tokens, pool_size): - assert factory.get_coins(swap.address) == [pt.address for pt in pool_tokens] - - def test_get_decimals(self, factory, swap, decimals): - assert factory.get_decimals(swap.address) == decimals - - def test_get_balances(self, factory, swap, pool_size): - assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] - - @pytest.mark.only_for_pool_type(0) - def test_get_underlying_balances(self, factory, swap): - with boa.reverts() as e: - factory.get_underlying_balances(swap.address) - assert str(e) == "dev: pool is not a metapool" - - def test_get_A(self, factory, swap): - assert factory.get_A(swap.address) == swap.A() - - def test_get_fees(self, factory, swap): - assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) - - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_get_coin_indices(self, factory, swap, sending, receiving, pool_tokens): - i, j, is_underlying = factory.get_coin_indices( - swap.address, pool_tokens[sending].address, pool_tokens[receiving].address - ) - assert i == sending - assert j == receiving - - def test_get_implementation_address(self, factory, swap, amm_implementation): - assert factory.get_implementation_address(swap.address) == amm_implementation.address - - def test_is_meta(self, factory, swap): - assert factory.is_meta(swap.address) is False - - def test_get_pool_types(self, factory, swap, pool_token_types): - assert factory.get_pool_asset_types(swap.address) == list(pool_token_types) - - @pytest.mark.only_for_pool_type(1) - class TestMeta: - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_find_pool_for_coins(self, factory, swap, underlying_tokens, sending, receiving): - assert ( - factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) - == swap.address - ) - - @pytest.mark.parametrize("idx", range(1, 4)) - def test_find_pool_for_coins_underlying(self, factory, swap, underlying_tokens, idx): - assert factory.find_pool_for_coins(underlying_tokens[0], underlying_tokens[idx]) == swap.address - assert factory.find_pool_for_coins(underlying_tokens[idx], underlying_tokens[0]) == swap.address - - def test_get_meta_n_coins(self, factory, swap): - assert factory.get_meta_n_coins(swap.address) == (2, 4) - - def test_get_underlying_coins(self, factory, swap, underlying_tokens): - tokens = [underlying_tokens[0]] + underlying_tokens[2:] - assert factory.get_underlying_coins(swap.address) == [t.address for t in tokens] - - def test_get_underlying_decimals(self, factory, swap, base_pool_decimals, pool_type): - assert factory.get_underlying_decimals(swap.address) == [18] + base_pool_decimals - - def test_get_metapool_rates(self, factory, swap, base_pool, initial_setup): - assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] - - def test_get_underlying_balances(self, factory, swap, base_pool, initial_setup): - assert factory.get_metapool_rates(swap.address) == [10**18, base_pool.get_virtual_price()] - - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) - def test_find_pool_underlying_base_pool_only( - self, factory, underlying_tokens, sending, receiving, zero_address - ): - assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address - - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(2, 5), 2)) - def test_get_coin_indices_underlying(self, factory, swap, sending, receiving, underlying_tokens): - i, j, is_underlying = factory.get_coin_indices( - swap, underlying_tokens[sending], underlying_tokens[receiving] - ) - assert i == sending - 1 - assert j == receiving - 1 - assert is_underlying is True - - @pytest.mark.parametrize("idx", range(1, 4)) - def test_get_coin_indices_reverts(self, factory, swap, base_pool_lp_token, underlying_tokens, idx): - with boa.reverts(): - factory.get_coin_indices(swap.address, base_pool_lp_token.address, underlying_tokens[idx]) - - def test_get_implementation_address(self, factory, swap, amm_implementation_meta): - assert factory.get_implementation_address(swap.address) == amm_implementation_meta.address - - def test_is_meta(self, factory, swap): - assert factory.is_meta(swap.address) is True - - class TestFactoryAddPools: - @pytest.fixture - def empty_factory(self, deployer, fee_receiver, owner): - with boa.env.prank(deployer): - _factory = boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) - return _factory - - @pytest.fixture - def empty_factory_with_implementations( - self, - empty_factory, - owner, - gauge_implementation, - views_implementation, - math_implementation, - amm_implementation, - amm_implementation_meta, - ): - with boa.env.prank(owner): - empty_factory.set_gauge_implementation(gauge_implementation.address) - empty_factory.set_views_implementation(views_implementation.address) - empty_factory.set_math_implementation(math_implementation.address) - - empty_factory.set_pool_implementations(0, amm_implementation.address) - empty_factory.set_metapool_implementations(0, amm_implementation_meta.address) - - return empty_factory - - def test_add_base_pool_already_exists( - self, - owner, - factory, - add_base_pool, - base_pool, - base_pool_lp_token, - base_pool_tokens, - ): - with boa.reverts(): - factory.add_base_pool( - base_pool.address, - base_pool_lp_token.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), - sender=owner, - ) - - def test_add_base_pool_only_admin( - self, - factory, - bob, - base_pool, - base_pool_lp_token, - base_pool_tokens, - ): - with boa.reverts(): - factory.add_base_pool( - base_pool.address, - base_pool_lp_token.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), - sender=bob, - ) - - def test_deploy_plain_pool( - self, empty_factory_with_implementations, amm_interface, pool_tokens, pool_size, zero_address - ): - swap_address = empty_factory_with_implementations.deploy_plain_pool( - "test", - "test", - [t.address for t in pool_tokens], - 2000, - 1000000, - 20000000000, - 866, - 0, - [0] * pool_size, - [bytes(b"")] * pool_size, - [zero_address] * pool_size, - ) - assert swap_address != zero_address - - swap = amm_interface.at(swap_address) - assert swap.coins(0) == pool_tokens[0].address - assert swap.coins(1) == pool_tokens[1].address - - assert swap.A() == 2000 - assert swap.fee() == 1000000 - - assert empty_factory_with_implementations.pool_count() == 1 - assert empty_factory_with_implementations.pool_list(0) == swap.address - assert empty_factory_with_implementations.get_decimals(swap) == [t.decimals() for t in pool_tokens] - - def test_pool_count( - self, - empty_factory_with_implementations, - swap, - add_base_pool, - amm_interface, - set_pool_implementations, - pool_tokens, - pool_size, - zero_address, - ): - assert empty_factory_with_implementations.pool_count() == 0 - - empty_factory_with_implementations.deploy_plain_pool( - "test", - "test", - [t.address for t in pool_tokens], - 2000, - 1000000, - 20000000000, - 866, - 0, - [0] * pool_size, - [bytes(b"")] * pool_size, - [zero_address] * pool_size, - ) - assert empty_factory_with_implementations.pool_count() == 1 From da9026b381c5269a17d02c744aba10d604ed6574 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 12 Jan 2024 08:57:00 +0100 Subject: [PATCH 283/337] Get rid of autouse markers that use swap --- pyproject.toml | 5 - tests/conftest.py | 66 +----- tests/factory/test_factory_forked.py | 3 +- tests/fixtures/pools.py | 159 ++++++------- tests/pools/meta/test_exchange_underlying.py | 15 +- .../meta/test_exchange_underlying_reverts.py | 106 +++++---- tests/pools/oracle/test_oracle.py | 221 +++++++++--------- tests/pools/test_donation_get_D.py | 25 +- tests/pools/test_oracles.py | 8 +- tests/pools/test_swap_getters.py | 12 +- 10 files changed, 271 insertions(+), 349 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7c62505b..1c2cf98b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,11 +62,6 @@ py_version = 310 known_first_party = "poetry" [tool.pytest.ini_options] -markers = [ - "only_for_pool_type: running tests only for specific pool types", - "only_for_token_types: running tests only if tokens of specific types are in pool", - "no_auto_generate: dont generate tests for this one", -] filterwarnings = [ "ignore:PytestUnknownMarkWarning" ] diff --git a/tests/conftest.py b/tests/conftest.py index ce7a4fb3..273b5334 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,7 @@ def pytest_generate_tests(metafunc): ) if "initial_decimals" in metafunc.fixturenames: + # this is only used in the decimals fixture metafunc.parametrize( "initial_decimals", decimal_types, @@ -99,69 +100,16 @@ def meta_decimals(metapool_token_type, decimals): return 18 if metapool_token_type == 1 else decimals[0] -# Usage -# @pytest.mark.only_for_token_types(1,2) -# -# will not be skipped only if at least one of tokens in pool is eth or oracle -# can be applied to classes -# -# @pytest.mark.only_for_token_types(2) -# class TestPoolsWithOracleToken: @pytest.fixture(autouse=True) -def skip_by_token_type(request, pool_tokens): - only_for_token_types = request.node.get_closest_marker("only_for_token_types") - if only_for_token_types: - asset_types = [tkn.asset_type() for tkn in pool_tokens] - if not any(asset_type in only_for_token_types.args for asset_type in asset_types): - pytest.skip("skipped because no tokens for these types") +def skip_rebasing(request, pool_token_types): + if request.node.get_closest_marker("skip_rebasing_tokens") and 2 in pool_token_types: + pytest.skip("skipped because test includes rebasing tokens") @pytest.fixture(autouse=True) -def skip_rebasing(request, swap): - only_for_token_types = request.node.get_closest_marker("skip_rebasing_tokens") - if only_for_token_types: - if 2 in get_asset_types_in_pool(swap): - pytest.skip("skipped because test includes rebasing tokens") - - -@pytest.fixture(autouse=True) -def skip_oracle(request, pool_tokens): - only_for_token_types = request.node.get_closest_marker("skip_oracle_tokens") - if only_for_token_types: - asset_types = [tkn.asset_type() for tkn in pool_tokens] - asset_types_contains_oracle = 1 in asset_types - if asset_types_contains_oracle: - pytest.skip("skipped because test includes oraclised tokens") - - -@pytest.fixture(autouse=True) -def only_oracle(request, pool_tokens): - only_for_token_types = request.node.get_closest_marker("only_oracle_tokens") - if only_for_token_types: - asset_types = [tkn.asset_type() for tkn in pool_tokens] - asset_types_contains_rebasing = 1 in asset_types - if not asset_types_contains_rebasing: - pytest.skip("skipped because test excludes oraclised tokens") - - -@pytest.fixture(autouse=True) -def only_rebasing(request, swap): - marker = request.node.get_closest_marker("contains_rebasing_tokens") - if marker: - asset_types_contains_rebasing = 2 in get_asset_types_in_pool(swap) - if not asset_types_contains_rebasing: - pytest.skip("skipped because test excludes rebasing tokens") - - -# Usage -# @pytest.mark.only_for_pool_type(1) -# class TestMetaPool... -@pytest.fixture(autouse=True) -def skip_by_pool_type(request, pool_type): - only_for_pool_type = request.node.get_closest_marker("only_for_pool_type") - if only_for_pool_type: - if pool_type not in only_for_pool_type.args: - pytest.skip("skipped because another pool type") +def skip_oracle(request, pool_token_types): + if request.node.get_closest_marker("skip_oracle_tokens") and 1 in pool_token_types: + pytest.skip("skipped because test includes oraclised tokens") @pytest.fixture(scope="module") diff --git a/tests/factory/test_factory_forked.py b/tests/factory/test_factory_forked.py index 1111de83..7f51ea3b 100644 --- a/tests/factory/test_factory_forked.py +++ b/tests/factory/test_factory_forked.py @@ -5,12 +5,11 @@ @pytest.fixture def empty_factory(deployer, fee_receiver, owner): with boa.env.prank(deployer): - _factory = boa.load( + return boa.load( "contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner, ) - return _factory def test_add_base_pool(empty_factory, owner, forked_chain): diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index c7632718..2613f1cb 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,109 +2,98 @@ import pytest from eth_utils import function_signature_to_4byte_selector +oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") +offpeg_fee_multiplier = 20000000000 + @pytest.fixture(scope="module") -def swap( - deployer, - factory, - pool_size, - pool_type, - pool_tokens, - zero_address, - amm_interface, - set_pool_implementations, - underlying_tokens, - base_pool, - amm_interface_meta, - add_base_pool, - set_metapool_implementations, -): - oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") - offpeg_fee_multiplier = 20000000000 - if pool_type == 0: - A = 2000 - fee = 1000000 - method_ids = [bytes(b"")] * pool_size - oracles = [zero_address] * pool_size - asset_types = [] - - for i in range(len(pool_tokens)): - - asset_type = pool_tokens[i].asset_type() - - if asset_type == 0: - A = 2000 - fee = 1000000 - asset_types.append(asset_type) - elif asset_type == 1: - A = 1000 - fee = 3000000 - asset_types.append(asset_type) - method_ids[i] = oracle_method_id - oracles[i] = pool_tokens[i].address - elif asset_type == 2: - A = 500 - fee = 4000000 - asset_types.append(asset_type) - - with boa.env.prank(deployer): - pool = factory.deploy_plain_pool( - "test", - "test", - [t.address for t in pool_tokens], - A, - fee, - offpeg_fee_multiplier, - 866, - 0, - asset_types, - method_ids, - oracles, - ) - return amm_interface.at(pool) - - elif pool_type == 1: - A = 2000 - fee = 1000000 - method_id = bytes(b"") - oracle = zero_address +def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_interface): + A = 2000 + fee = 1000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + asset_types = [] - metapool_token = underlying_tokens[0] - asset_type = metapool_token.asset_type() # 0 = Plain, 1 = Oracle, 2 = Rebasing + for i, token in enumerate(pool_tokens): + asset_type = token.asset_type() if asset_type == 0: A = 2000 fee = 1000000 - + asset_types.append(asset_type) elif asset_type == 1: A = 1000 fee = 3000000 - method_id = oracle_method_id - oracle = metapool_token.address - + asset_types.append(asset_type) + method_ids[i] = oracle_method_id + oracles[i] = token.address elif asset_type == 2: A = 500 fee = 4000000 + asset_types.append(asset_type) - pool = factory.deploy_metapool( - base_pool.address, # _base_pool: address - "test", # _name: String[32], - "test", # _symbol: String[10], - metapool_token.address, # _coin: address, - A, # _A: uint256, - fee, # _fee: uint256, + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in pool_tokens], + A, + fee, offpeg_fee_multiplier, - 866, # _ma_exp_time: uint256, - 0, # _implementation_idx: uint256 - asset_type, # _asset_type: uint8 - method_id, # _method_id: bytes4 - oracle, # _oracle: address + 866, + 0, + asset_types, + method_ids, + oracles, ) + return amm_interface.at(pool) - return amm_interface_meta.at(pool) - else: - raise ValueError("Wrong pool type") +@pytest.fixture(scope="module") +def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface_meta): + A = 2000 + fee = 1000000 + method_id = bytes(b"") + oracle = zero_address + + metapool_token = underlying_tokens[0] + asset_type = metapool_token.asset_type() # 0 = Plain, 1 = Oracle, 2 = Rebasing + + if asset_type == 0: + A = 2000 + fee = 1000000 + + elif asset_type == 1: + A = 1000 + fee = 3000000 + method_id = oracle_method_id + oracle = metapool_token.address + + elif asset_type == 2: + A = 500 + fee = 4000000 + + pool = factory.deploy_metapool( + base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + metapool_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return amm_interface_meta.at(pool) + + +@pytest.fixture(scope="module") +def swap(basic_swap, meta_swap, pool_type): + return {0: basic_swap, 1: meta_swap}[pool_type] # <--------------------- Metapool configuration ---------------------> diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 64c84f19..55943bf0 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -7,12 +7,10 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -@pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.skip_oracle_tokens @pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) -def test_amounts(bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): - +def test_amounts(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] amount_sent = 10 ** underlying_decimals[sending] @@ -20,22 +18,21 @@ def test_amounts(bob, swap, underlying_tokens, sending, receiving, meta_decimals if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: underlying_tokens[sending]._mint_for_testing(bob, amount_sent) - expected_received = swap.get_dy_underlying(sending, receiving, amount_sent) - received_true = swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 + expected_received = meta_swap.get_dy_underlying(sending, receiving, amount_sent) + received_true = meta_swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 assert approx(received_true, expected_received, 1e-3) -@pytest.mark.only_for_pool_type(1) # only for metapools @pytest.mark.skip_rebasing_tokens @pytest.mark.skip_oracle_tokens @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_min_dy_underlying(bob, swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): +def test_min_dy_underlying(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] amount = 10 ** underlying_decimals[sending] underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - expected = swap.get_dy_underlying(sending, receiving, amount) - received = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + expected = meta_swap.get_dy_underlying(sending, receiving, amount) + received = meta_swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert approx(expected, received, 1e-3) diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py index 41175ba2..39c24e2b 100644 --- a/tests/pools/meta/test_exchange_underlying_reverts.py +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -6,54 +6,58 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -@pytest.mark.only_for_pool_type(1) # only for metapools -class TestMetaExchangeUnderlyingReverts: - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) - def test_min_dy_too_high(self, bob, swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving): - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10 ** underlying_decimals[sending] - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - - min_dy = swap.get_dy_underlying(sending, receiving, int(amount * 1.0001)) - with boa.reverts(): - swap.exchange_underlying(sending, receiving, amount, min_dy, sender=bob) - - @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) - def test_insufficient_balance( - self, bob, swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address - ): - underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] - amount = 10 ** underlying_decimals[sending] - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - underlying_tokens[sending].transfer(zero_address, underlying_tokens[sending].balanceOf(bob), sender=bob) - - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) - with boa.reverts(): - swap.exchange_underlying(sending, receiving, amount + 1, 0, sender=bob) - - @pytest.mark.parametrize("idx", range(4)) - def test_same_coin(self, bob, swap, idx): - with boa.reverts(): - swap.exchange_underlying(idx, idx, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [-1, -(2**127)]) - def test_i_below_zero(self, bob, swap, idx): - with boa.reverts(): - swap.exchange_underlying(idx, 0, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [4, 2**127 - 1]) - def test_i_above_n_coins(self, bob, swap, idx): - with boa.reverts(): - swap.exchange_underlying(idx, 0, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [-1, -(2**127)]) - def test_j_below_zero(self, bob, swap, idx): - with boa.reverts(): - swap.exchange_underlying(0, idx, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [4, 2**127 - 1]) - def test_j_above_n_coins(self, bob, swap, idx): - with boa.reverts(): - swap.exchange_underlying(0, idx, 0, 0, sender=bob) +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +def test_min_dy_too_high(bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + + min_dy = meta_swap.get_dy_underlying(sending, receiving, int(amount * 1.0001)) + with boa.reverts(): + meta_swap.exchange_underlying(sending, receiving, amount, min_dy, sender=bob) + + +@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +def test_insufficient_balance( + bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address +): + underlying_decimals = [meta_decimals] + base_pool_decimals + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + amount = 10 ** underlying_decimals[sending] + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + underlying_tokens[sending].transfer(zero_address, underlying_tokens[sending].balanceOf(bob), sender=bob) + + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + with boa.reverts(): + meta_swap.exchange_underlying(sending, receiving, amount + 1, 0, sender=bob) + + +@pytest.mark.parametrize("idx", range(4)) +def test_same_coin(bob, meta_swap, idx): + with boa.reverts(): + meta_swap.exchange_underlying(idx, idx, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [-1, -(2 ** 127)]) +def test_i_below_zero(bob, meta_swap, idx): + with boa.reverts(): + meta_swap.exchange_underlying(idx, 0, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [4, 2 ** 127 - 1]) +def test_i_above_n_coins(bob, meta_swap, idx): + with boa.reverts(): + meta_swap.exchange_underlying(idx, 0, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [-1, -(2 ** 127)]) +def test_j_below_zero(bob, meta_swap, idx): + with boa.reverts(): + meta_swap.exchange_underlying(0, idx, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [4, 2 ** 127 - 1]) +def test_j_above_n_coins(bob, meta_swap, idx): + with boa.reverts(): + meta_swap.exchange_underlying(0, idx, 0, 0, sender=bob) diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index b6257905..24108a2c 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -8,122 +8,119 @@ DEPOSIT_AMOUNT = INITIAL_AMOUNT // 100 -@pytest.mark.only_for_token_types(1) -class TestOracle: - class TestInitialLiquidity: - @pytest.fixture(scope="module") - def initial_setup_alice( - self, - alice, - deposit_amounts, - swap, - pool_type, - base_pool, - base_pool_tokens, - base_pool_decimals, - base_pool_lp_token, - initial_balance, - initial_amounts, - pool_tokens, - underlying_tokens, - ): - with boa.env.anchor(): - mint_for_testing(alice, 1 * 10**18, None, True) - - if pool_type == 0: - mint_account(alice, pool_tokens, initial_balance, initial_amounts) - with boa.env.prank(alice): - for token in pool_tokens: - token.approve(swap.address, 2**256 - 1) - - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) - - with boa.env.prank(alice): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) - - yield - - def test_initial_liquidity( - self, - alice, - initial_setup_alice, - swap, - pool_type, - pool_token_types, - metapool_token_type, - decimals, - meta_decimals, - pool_tokens, - metapool_token, - ): - amounts = [] - - if pool_type == 0: - for i, t in enumerate(pool_token_types): - if t != 1: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) - else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // pool_tokens[i].exchangeRate()) +@pytest.fixture(scope="module") +def initial_setup_alice( + alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_oracle_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_balance, + initial_amounts, + oracle_tokens, + underlying_tokens, +): + with boa.env.anchor(): + mint_for_testing(alice, 1 * 10 ** 18, None, True) + + if pool_type == 0: + mint_account(alice, oracle_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in oracle_tokens: + token.approve(swap.address, 2 ** 256 - 1) + + else: + add_base_pool_liquidity(alice, base_pool, base_oracle_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(swap.address, 2 ** 256 - 1) + + yield + + +def test_initial_liquidity( + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + oracle_tokens, + metapool_token, +): + amounts = [] + + if pool_type == 0: + for i, t in enumerate(pool_token_types): + if t != 1: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) else: - if metapool_token_type == 1: - amounts = [ - DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), - DEPOSIT_AMOUNT * 10**18, - ] - else: - amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] - - swap.add_liquidity(amounts, 0, sender=alice) - swap.add_liquidity(amounts, 0, sender=alice) - - assert swap.admin_balances(0) == 0 - assert swap.admin_balances(1) == 0 - - def test_oracles(self, alice, swap, pool_size, pool_type, pool_token_types, metapool_token_type): - assert swap._storage.oracles.get() != [0] * pool_size - - def test_get_dy( - self, - alice, - initial_setup_alice, - swap, - pool_type, - pool_token_types, - metapool_token_type, - decimals, - meta_decimals, - pool_tokens, - metapool_token, - ): - amounts = [] - - if pool_type == 0: - for i, t in enumerate(pool_token_types): - if t != 1: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) - else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // pool_tokens[i].exchangeRate()) + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10 ** 18 // oracle_tokens[i].exchangeRate()) + else: + if metapool_token_type == 1: + amounts = [ + DEPOSIT_AMOUNT * 10 ** meta_decimals * 10 ** 18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10 ** 18, + ] + else: + amounts = [DEPOSIT_AMOUNT * 10 ** meta_decimals, DEPOSIT_AMOUNT * 10 ** 18] + + swap.add_liquidity(amounts, 0, sender=alice) + swap.add_liquidity(amounts, 0, sender=alice) + + assert swap.admin_balances(0) == 0 + assert swap.admin_balances(1) == 0 + + +def test_oracles(alice, swap, pool_size, pool_type, pool_token_types, metapool_token_type): + assert swap._storage.oracles.get() != [0] * pool_size + + +def test_get_dy( + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + oracle_tokens, + metapool_token, +): + amounts = [] + + if pool_type == 0: + for i, t in enumerate(pool_token_types): + if t != 1: + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) else: - if metapool_token_type == 1: - amounts = [ - DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), - DEPOSIT_AMOUNT * 10**18, - ] - else: - amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10 ** 18 // oracle_tokens[i].exchangeRate()) + else: + if metapool_token_type == 1: + amounts = [ + DEPOSIT_AMOUNT * 10 ** meta_decimals * 10 ** 18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10 ** 18, + ] + else: + amounts = [DEPOSIT_AMOUNT * 10 ** meta_decimals, DEPOSIT_AMOUNT * 10 ** 18] - swap.add_liquidity(amounts, 0, sender=alice) + swap.add_liquidity(amounts, 0, sender=alice) - if pool_type == 0: - rate_1 = 10**18 if pool_token_types[0] != 1 else pool_tokens[0].exchangeRate() - rate_2 = 10**18 if pool_token_types[1] != 1 else pool_tokens[1].exchangeRate() + if pool_type == 0: + rate_1 = 10 ** 18 if pool_token_types[0] != 1 else oracle_tokens[0].exchangeRate() + rate_2 = 10 ** 18 if pool_token_types[1] != 1 else oracle_tokens[1].exchangeRate() - assert swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) + assert swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) - else: - rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() + else: + rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() - assert swap.get_dy(0, 1, 10**18) == pytest.approx(rate_1, rel=1e-3) + assert swap.get_dy(0, 1, 10 ** 18) == pytest.approx(rate_1, rel=1e-3) diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index 1e700dde..12f87c2c 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -1,34 +1,31 @@ import boa -import pytest from tests.utils import get_asset_types_in_pool from tests.utils.tokens import mint_for_testing -# @pytest.mark.skip_rebasing_tokens -@pytest.mark.only_for_pool_type(0) -def test_donate_get_D(bob, swap, underlying_tokens, pool_tokens): +def test_donate_get_D(bob, basic_swap, underlying_tokens, pool_tokens): # check if pool is empty: - for i in range(swap.N_COINS()): - assert swap.balances(i) == 0 + for i in range(basic_swap.N_COINS()): + assert basic_swap.balances(i) == 0 # adding liquidity should not bork: - amounts = [10**17] * swap.N_COINS() + amounts = [10**17] * basic_swap.N_COINS() for token in pool_tokens: - token.approve(swap, 2**256 - 1, sender=bob) + token.approve(basic_swap, 2**256 - 1, sender=bob) mint_for_testing(bob, 10**18, token, False) # check if pool is empty (after minting tokenss): - for i in range(swap.N_COINS()): - assert swap.balances(i) == 0 + for i in range(basic_swap.N_COINS()): + assert basic_swap.balances(i) == 0 # donate 1 wei and attempt adding liquidity: - pool_tokens[0].transfer(swap, 10, sender=bob) - if 2 in get_asset_types_in_pool(swap): + pool_tokens[0].transfer(basic_swap, 10, sender=bob) + if 2 in get_asset_types_in_pool(basic_swap): with boa.reverts(): # division by zero error expected for rebasing implementation - swap.add_liquidity(amounts, 0, sender=bob) + basic_swap.add_liquidity(amounts, 0, sender=bob) else: - swap.add_liquidity(amounts, 0, sender=bob) + basic_swap.add_liquidity(amounts, 0, sender=bob) diff --git a/tests/pools/test_oracles.py b/tests/pools/test_oracles.py index 9b732bb4..960c0087 100644 --- a/tests/pools/test_oracles.py +++ b/tests/pools/test_oracles.py @@ -146,9 +146,7 @@ def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amo amount=strategy("uint256", min_value=10**9, max_value=10**15), ) @settings(**SETTINGS) -@pytest.mark.only_for_pool_type(0) -def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount): - +def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimals, amount): # calc amount in: amount_in = amount * 10 ** (decimals[0]) @@ -158,7 +156,7 @@ def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amo # do large swap try: - swap.exchange(0, 1, amount_in, 0, sender=bob) + basic_swap.exchange(0, 1, amount_in, 0, sender=bob) except boa.BoaError: return # we're okay with failure to manipulate here @@ -166,7 +164,7 @@ def test_manipulate_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amo boa.env.time_travel(blocks=500) # check if price oracle is way too high - p_oracle_after = swap.price_oracle(0) + p_oracle_after = basic_swap.price_oracle(0) assert p_oracle_after < 2 * 10**18 diff --git a/tests/pools/test_swap_getters.py b/tests/pools/test_swap_getters.py index dfccf6e2..e4535c6c 100644 --- a/tests/pools/test_swap_getters.py +++ b/tests/pools/test_swap_getters.py @@ -27,16 +27,14 @@ def test_get_dx(i, j, amount_in, swap, factory, initial_setup): assert _amount_in == pytest.approx(approx_in, 1e-2) -@pytest.mark.only_for_pool_type(1) # only for metapools @given( amount_in=strategy("decimal", min_value=0.001, max_value=10**6), i=strategy("uint", min_value=0, max_value=4), j=strategy("uint", min_value=0, max_value=4), ) @settings(**SETTINGS) -def test_get_dx_underlying(i, j, amount_in, swap, factory, initial_setup): - - base_n_coins = swap.BASE_N_COINS() +def test_get_dx_underlying(i, j, amount_in, meta_swap, factory, initial_setup): + base_n_coins = meta_swap.BASE_N_COINS() if i == j: return @@ -48,10 +46,10 @@ def test_get_dx_underlying(i, j, amount_in, swap, factory, initial_setup): if min(i, j) > 0: # base pool swap: it reverts in view contract return - _token_i_precision = 10 ** factory.get_underlying_decimals(swap)[i] + _token_i_precision = 10 ** factory.get_underlying_decimals(meta_swap)[i] _amount_in = int(amount_in * _token_i_precision) - expected_out = swap.get_dy_underlying(i, j, _amount_in) - approx_in = swap.get_dx_underlying(i, j, expected_out) + expected_out = meta_swap.get_dy_underlying(i, j, _amount_in) + approx_in = meta_swap.get_dx_underlying(i, j, expected_out) # not accurate, but close enough: assert _amount_in == pytest.approx(approx_in, 1e-2) From 729847ea71e283087a878604dd16193f48a43e07 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 12 Jan 2024 09:43:59 +0100 Subject: [PATCH 284/337] Parametrization should happen on function level --- tests/conftest.py | 53 ++++----------- tests/factory/test_factory_add_pools.py | 11 ++- tests/fixtures/accounts.py | 44 ++++++------ tests/fixtures/factory.py | 14 ++-- tests/fixtures/pools.py | 8 +-- tests/fixtures/tokens.py | 26 ++++---- tests/gauge/test_rewards.py | 70 ++++++++++---------- tests/pools/meta/test_exchange_underlying.py | 2 +- 8 files changed, 99 insertions(+), 129 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 273b5334..17e882a3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,6 @@ import boa import pytest -from tests.utils import get_asset_types_in_pool - pytest_plugins = [ "tests.fixtures.accounts", "tests.fixtures.constants", @@ -21,6 +19,11 @@ decimal_types = [(18, 18), (10, 12)] +@pytest.fixture(scope="session", autouse=True) +def fast_mode(): + boa.env.enable_fast_mode() + + def pytest_generate_tests(metafunc): if "pool_type" in metafunc.fixturenames: pool_type_items = sorted(pool_types.items()) @@ -28,16 +31,18 @@ def pytest_generate_tests(metafunc): "pool_type", [v for k, v in pool_type_items], ids=[f"(PoolType={k})" for k, v in pool_type_items], - indirect=True, # to declare the fixture scope ) if "pool_token_types" in metafunc.fixturenames: - combinations = sorted(itertools.combinations_with_replacement(token_types.items(), 2)) + items = [ + (k, v) for k, v in token_types.items() + if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") + ] + combinations = sorted(itertools.combinations_with_replacement(items, 2)) metafunc.parametrize( "pool_token_types", [(v1, v2) for (k1, v1), (k2, v2) in combinations], ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in combinations], - indirect=True, # to declare the fixture scope ) if "metapool_token_type" in metafunc.fixturenames: @@ -47,7 +52,6 @@ def pytest_generate_tests(metafunc): "metapool_token_type", [v for k, v in token_type_items], ids=[f"(MetaTokenType={k})" for k, v in token_type_items], - indirect=True, # to declare the fixture scope ) if "initial_decimals" in metafunc.fixturenames: @@ -56,7 +60,6 @@ def pytest_generate_tests(metafunc): "initial_decimals", decimal_types, ids=[f"(Decimals={i},{j})" for i, j in decimal_types], - indirect=True, # to declare the fixture scope ) @@ -65,27 +68,7 @@ def pool_size(): return 2 -@pytest.fixture(scope="session") -def pool_type(request): # to declare the fixture scope - return request.param - - -@pytest.fixture(scope="session") -def pool_token_types(request): # to declare the fixture scope - return request.param - - -@pytest.fixture(scope="session") -def metapool_token_type(request): # to declare the fixture scope - return request.param - - -@pytest.fixture(scope="session") -def initial_decimals(request): # to declare the fixture scope - return request.param - - -@pytest.fixture(scope="session") +@pytest.fixture() def decimals(initial_decimals, pool_token_types): return [ # oracle tokens are always 18 decimals @@ -94,24 +77,12 @@ def decimals(initial_decimals, pool_token_types): ] -@pytest.fixture(scope="session") +@pytest.fixture() def meta_decimals(metapool_token_type, decimals): # oracle tokens are always 18 decimals return 18 if metapool_token_type == 1 else decimals[0] -@pytest.fixture(autouse=True) -def skip_rebasing(request, pool_token_types): - if request.node.get_closest_marker("skip_rebasing_tokens") and 2 in pool_token_types: - pytest.skip("skipped because test includes rebasing tokens") - - -@pytest.fixture(autouse=True) -def skip_oracle(request, pool_token_types): - if request.node.get_closest_marker("skip_oracle_tokens") and 1 in pool_token_types: - pytest.skip("skipped because test includes oraclised tokens") - - @pytest.fixture(scope="module") def forked_chain(): rpc_url = os.getenv("WEB3_PROVIDER_URL") diff --git a/tests/factory/test_factory_add_pools.py b/tests/factory/test_factory_add_pools.py index 47720686..7d41be68 100644 --- a/tests/factory/test_factory_add_pools.py +++ b/tests/factory/test_factory_add_pools.py @@ -70,12 +70,12 @@ def test_add_base_pool_only_admin( def test_deploy_plain_pool( - empty_factory_with_implementations, amm_interface, pool_tokens, pool_size, zero_address + empty_factory_with_implementations, amm_interface, plain_tokens, pool_size, zero_address ): swap_address = empty_factory_with_implementations.deploy_plain_pool( "test", "test", - [t.address for t in pool_tokens], + [t.address for t in (plain_tokens)], 2000, 1000000, 20000000000, @@ -88,20 +88,19 @@ def test_deploy_plain_pool( assert swap_address != zero_address swap = amm_interface.at(swap_address) - assert swap.coins(0) == pool_tokens[0].address - assert swap.coins(1) == pool_tokens[1].address + assert swap.coins(0) == plain_tokens[0].address + assert swap.coins(1) == plain_tokens[1].address assert swap.A() == 2000 assert swap.fee() == 1000000 assert empty_factory_with_implementations.pool_count() == 1 assert empty_factory_with_implementations.pool_list(0) == swap.address - assert empty_factory_with_implementations.get_decimals(swap) == [t.decimals() for t in pool_tokens] + assert empty_factory_with_implementations.get_decimals(swap) == [t.decimals() for t in (plain_tokens)] def test_pool_count( empty_factory_with_implementations, - swap, add_base_pool, amm_interface, set_pool_implementations, diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 63e9f4c8..8e5bba2b 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -9,57 +9,57 @@ from .constants import INITIAL_AMOUNT -@pytest.fixture(scope="module") +@pytest.fixture() def deployer(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def owner(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def fee_receiver(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def eth_acc() -> LocalAccount: return Account.create() -@pytest.fixture(scope="module") +@pytest.fixture() def alice(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def bob(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def charlie(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def dave(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def erin(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def frank(): return boa.env.generate_address() -@pytest.fixture(scope="module") +@pytest.fixture() def accounts(bob, charlie, dave, erin, frank): return [bob, charlie, dave, erin, frank] @@ -77,32 +77,32 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="module") +@pytest.fixture() def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): mint_account(owner, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture() def approve_owner(owner, pool_tokens, swap): approve_account(owner, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture() def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): mint_account(alice, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture() def approve_alice(alice, pool_tokens, swap): approve_account(alice, pool_tokens, swap) -@pytest.fixture(scope="module") +@pytest.fixture() def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): mint_account(bob, pool_tokens, initial_balance, initial_amounts) -@pytest.fixture(scope="module") +@pytest.fixture() def approve_bob(bob, pool_tokens, swap): approve_account(bob, pool_tokens, swap) @@ -119,7 +119,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal base_pool.add_liquidity(amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture() def add_initial_liquidity_owner( owner, approve_owner, @@ -149,7 +149,7 @@ def add_initial_liquidity_owner( swap.add_liquidity([to_mint_token0, lp_token_bal], 0) -@pytest.fixture(scope="module") +@pytest.fixture() def add_initial_liquidity_alice( alice, approve_alice, @@ -172,7 +172,7 @@ def add_initial_liquidity_alice( swap.add_liquidity(deposit_amounts, 0) -@pytest.fixture(scope="module") +@pytest.fixture() def mint_meta_bob( bob, mint_bob, @@ -188,14 +188,14 @@ def mint_meta_bob( assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) -@pytest.fixture(scope="module") +@pytest.fixture() def approve_meta_bob(bob, underlying_tokens, swap): with boa.env.prank(bob): for token in underlying_tokens: token.approve(swap.address, 2**256 - 1) -@pytest.fixture(scope="module") +@pytest.fixture() def initial_setup( alice, bob, diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 8f4baf72..e6d8896f 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -59,18 +59,18 @@ def factory( math_implementation, ): with boa.env.prank(deployer): - _factory = boa.load( + factory = boa.load( "contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner, ) with boa.env.prank(owner): - _factory.set_gauge_implementation(gauge_implementation.address) - _factory.set_views_implementation(views_implementation.address) - _factory.set_math_implementation(math_implementation.address) + factory.set_gauge_implementation(gauge_implementation.address) + factory.set_views_implementation(views_implementation.address) + factory.set_math_implementation(math_implementation.address) - return _factory + return factory # <--------------------- Functions ---------------------> @@ -86,7 +86,7 @@ def set_metapool_implementations(owner, factory, amm_implementation_meta): factory.set_metapool_implementations(0, amm_implementation_meta.address) -@pytest.fixture(scope="module") +@pytest.fixture() def add_base_pool( owner, factory, @@ -121,7 +121,7 @@ def set_math_implementation(owner, factory, math_implementation): factory.set_math_implementation(math_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture() def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): with boa.env.prank(owner): gauge_address = factory.deploy_gauge(swap.address) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 2613f1cb..7b4e93d9 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -6,7 +6,7 @@ offpeg_fee_multiplier = 20000000000 -@pytest.fixture(scope="module") +@pytest.fixture() def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_interface): A = 2000 fee = 1000000 @@ -49,7 +49,7 @@ def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_inte return amm_interface.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface_meta): A = 2000 fee = 1000000 @@ -91,13 +91,13 @@ def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface return amm_interface_meta.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def swap(basic_swap, meta_swap, pool_type): return {0: basic_swap, 1: meta_swap}[pool_type] # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture() def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): with boa.env.prank(deployer): base_pool = boa.load( diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 57bbcb49..86964f0b 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture() def plain_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -11,7 +11,7 @@ def plain_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture() def oracle_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -36,7 +36,7 @@ def oracle_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture() def rebase_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): @@ -45,24 +45,24 @@ def rebase_tokens(deployer, decimals): return tokens -@pytest.fixture(scope="module") +@pytest.fixture() def pool_tokens(pool_token_types, plain_tokens, oracle_tokens, rebase_tokens): tokens = {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens} return [tokens[t][i] for i, t in enumerate(pool_token_types)] # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture() def metapool_token(metapool_token_type, plain_tokens, oracle_tokens, rebase_tokens): return {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens}[metapool_token_type][0] -@pytest.fixture(scope="module") +@pytest.fixture() def base_pool_decimals(): return [18, 18, 18] -@pytest.fixture(scope="module") +@pytest.fixture() def base_pool_tokens(deployer, base_pool_decimals): with boa.env.prank(deployer): return [ @@ -71,37 +71,37 @@ def base_pool_tokens(deployer, base_pool_decimals): ] -@pytest.fixture(scope="module") +@pytest.fixture() def base_pool_lp_token(deployer): with boa.env.prank(deployer): return boa.load("contracts/mocks/CurveTokenV3.vy", "LP", "LP") -@pytest.fixture(scope="module") +@pytest.fixture() def underlying_tokens(metapool_token, base_pool_tokens, base_pool_lp_token): return [metapool_token, base_pool_lp_token, *base_pool_tokens] # <--------------------- Gauge rewards ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture() def coin_reward(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CR", "CR", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def coin_reward_a(owner, mint_owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRa", "CRa", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def coin_reward_b(owner): with boa.env.prank(owner): return boa.load("contracts/mocks/ERC20.vy", "CRb", "CRb", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def coin_rewards_additional(owner): coins = [] with boa.env.prank(owner): diff --git a/tests/gauge/test_rewards.py b/tests/gauge/test_rewards.py index 450ee3f8..5bac73f0 100644 --- a/tests/gauge/test_rewards.py +++ b/tests/gauge/test_rewards.py @@ -1,39 +1,39 @@ import boa import pytest -REWARD = 10**20 +REWARD = 10 ** 20 WEEK = 7 * 86400 -LP_AMOUNT = 10**18 - - -@pytest.mark.usefixtures("forked_chain") -class TestGaugeRewards: - class TestAddRewards: - @pytest.fixture() - def initial_setup(self, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation): - with boa.env.prank(owner): - swap.approve(gauge.address, LP_AMOUNT) - gauge.deposit(LP_AMOUNT) - - def test_set_rewards_no_deposit(self, owner, coin_reward, swap, gauge, zero_address): - with boa.env.prank(owner): - gauge.add_reward(coin_reward.address, owner) - - assert swap.balanceOf(gauge.address) == LP_AMOUNT - assert gauge.reward_tokens(0) == coin_reward.address - assert gauge.reward_tokens(1) == zero_address - - def test_multiple_reward_tokens(self, owner, coin_reward, coin_reward_a, coin_reward_b, gauge): - coins = [coin_reward.address, coin_reward_a.address, coin_reward_b.address] - with boa.env.prank(owner): - for coin in coins: - gauge.add_reward(coin, owner) - - assert coins == [gauge.reward_tokens(i) for i in range(3)] - - def test_cant_exceed_max(self, owner, coin_rewards_additional, gauge): - with boa.env.prank(owner): - for i in range(8): - gauge.add_reward(coin_rewards_additional[i].address, owner) - with boa.reverts(): - gauge.add_reward(coin_rewards_additional[i].address, owner) +LP_AMOUNT = 10 ** 18 + + +@pytest.fixture(autouse=True) +def initial_setup(forked_chain, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation): + with boa.env.prank(owner): + swap.approve(gauge.address, LP_AMOUNT) + gauge.deposit(LP_AMOUNT) + + +def test_set_rewards_no_deposit(owner, coin_reward, swap, gauge, zero_address): + with boa.env.prank(owner): + gauge.add_reward(coin_reward.address, owner) + + assert swap.balanceOf(gauge.address) == LP_AMOUNT + assert gauge.reward_tokens(0) == coin_reward.address + assert gauge.reward_tokens(1) == zero_address + + +def test_multiple_reward_tokens(owner, coin_reward, coin_reward_a, coin_reward_b, gauge): + coins = [coin_reward.address, coin_reward_a.address, coin_reward_b.address] + with boa.env.prank(owner): + for coin in coins: + gauge.add_reward(coin, owner) + + assert coins == [gauge.reward_tokens(i) for i in range(3)] + + +def test_cant_exceed_max(owner, coin_rewards_additional, gauge): + with boa.env.prank(owner): + for i in range(8): + gauge.add_reward(coin_rewards_additional[i].address, owner) + with boa.reverts(): + gauge.add_reward(coin_rewards_additional[i].address, owner) diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 55943bf0..2ed2c246 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -4,7 +4,7 @@ from tests.utils import approx -pytestmark = pytest.mark.usefixtures("initial_setup") +pytest.mark.usefixtures("initial_setup") @pytest.mark.skip_oracle_tokens From f402cea1aa5e8aa43d8b891d79cbe4a89febda2e Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 12 Jan 2024 10:01:08 +0100 Subject: [PATCH 285/337] Split token tests --- .github/workflows/ci.yaml | 13 +- README.MD | 2 +- tests/test_token.py | 370 ------------------------ tests/token/__init__.py | 0 tests/{ => token}/test_get_D.py | 0 tests/token/test_token_approve.py | 156 ++++++++++ tests/token/test_token_transfer.py | 86 ++++++ tests/token/test_token_transfer_from.py | 170 +++++++++++ 8 files changed, 414 insertions(+), 383 deletions(-) delete mode 100644 tests/test_token.py create mode 100644 tests/token/__init__.py rename tests/{ => token}/test_get_D.py (100%) create mode 100644 tests/token/test_token_approve.py create mode 100644 tests/token/test_token_transfer.py create mode 100644 tests/token/test_token_transfer_from.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2df74611..e5ac2182 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,17 +30,6 @@ jobs: - gauge - pools - factory - include: - - name: gauge - tests: tests/gauge/ - - name: pools - tests: tests/pools/ - - name: factory - tests: tests/factory/ - - name: other - tests: >- - tests/test_get_D.py - tests/test_token.py steps: - uses: actions/checkout@v3 @@ -67,4 +56,4 @@ jobs: WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} run: | source .venv/bin/activate - pytest -n auto ${{ matrix.tests }} + pytest -n auto tests/${{ matrix.name }}/ diff --git a/README.MD b/README.MD index 4d7ee39d..61d428f0 100644 --- a/README.MD +++ b/README.MD @@ -45,7 +45,7 @@ pytest tests/factory/ Testing swap is ERC20 ```shell -pytest tests/test_token.py +pytest tests/token/ ``` Testing swaps diff --git a/tests/test_token.py b/tests/test_token.py deleted file mode 100644 index c3283391..00000000 --- a/tests/test_token.py +++ /dev/null @@ -1,370 +0,0 @@ -import boa -import pytest -from eip712.messages import EIP712Message -from eth_account._utils.signing import to_bytes32 - -from tests.utils.transactions import call_returning_result_and_logs - -added_liquidity = pytest.mark.usefixtures("initial_setup") - - -class TestPoolToken: - class TestTokenApprove: - @pytest.mark.parametrize("idx", range(4)) - def test_initial_approval_is_zero(self, swap, alice, accounts, idx): - assert swap.allowance(alice, accounts[idx]) == 0 - - def test_approve(self, swap, alice, bob): - swap.approve(bob, 10**19, sender=alice) - assert swap.allowance(alice, bob) == 10**19 - - def test_modify_approve_zero_nonzero(self, swap, alice, bob): - with boa.env.prank(alice): - swap.approve(bob, 10**19) - swap.approve(bob, 0) - swap.approve(bob, 12345678) - - assert swap.allowance(alice, bob) == 12345678 - - def test_revoke_approve(self, swap, alice, bob): - with boa.env.prank(alice): - swap.approve(bob, 10**19) - swap.approve(bob, 0) - - assert swap.allowance(alice, bob) == 0 - - def test_approve_self(self, swap, alice): - swap.approve(alice, 10**19, sender=alice) - assert swap.allowance(alice, alice) == 10**19 - - def test_only_affects_target(self, swap, alice, bob): - swap.approve(bob, 10**19, sender=alice) - assert swap.allowance(bob, alice) == 0 - - def test_returns_true(self, swap, alice, bob): - tx = swap.approve(bob, 10**19, sender=alice) - assert tx is True - - def test_approval_event_fires(self, alice, bob, swap): - value = 10**19 - res, events = call_returning_result_and_logs(swap, "approve", bob, value, sender=alice) - - assert res is True - assert len(events) == 1 - assert repr(events[0]) == f"Approval(owner={alice}, spender={bob}, value={value})" - - @added_liquidity - def test_infinite_approval(self, swap, alice, bob): - swap.approve(bob, 2**256 - 1, sender=alice) - swap.transferFrom(alice, bob, 10**18, sender=bob) - - assert swap.allowance(alice, bob) == 2**256 - 1 - - @staticmethod - def permit_class(swap) -> type[EIP712Message]: - class Permit(EIP712Message): - # EIP-712 Domain Fields - _name_: "string" = swap.name() # noqa: F821 - _version_: "string" = swap.version() # noqa: F821 - _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 - _verifyingContract_: "address" = swap.address # noqa: F821 - _salt_: "bytes32" = swap.salt() # noqa: F821 - - # EIP-2612 Data Fields - owner: "address" # noqa: F821 - spender: "address" # noqa: F821 - value: "uint256" # noqa: F821 - nonce: "uint256" # noqa: F821 - deadline: "uint256" = 2**256 - 1 # noqa: F821 - - return Permit - - def test_permit(self, eth_acc, bob, swap): - value = 2**256 - 1 - permit = self.permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) - sig = eth_acc.sign_message(permit.signable_message) - - res, events = call_returning_result_and_logs( - swap, - "permit", - eth_acc.address, - bob, - 2**256 - 1, - 2**256 - 1, - sig.v, - to_bytes32(sig.r), - to_bytes32(sig.s), - sender=bob, - ) - - assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 - assert res is True - assert len(events) == 1 - assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" - assert swap.nonces(eth_acc.address) == 1 - - def test_permit_contract(self, eth_acc, bob, swap): - # based on https://eips.ethereum.org/EIPS/eip-1271 - src = """ - # @version ^0.3.9 - OWNER: public(immutable(address)) - - @external - def __init__(): - OWNER = msg.sender - - @view - @external - def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: - signer: address = self._recover_signer(_hash, _signature) - if signer == OWNER: - return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 - return 0xffffffff00000000000000000000000000000000000000000000000000000000 - - @view - @internal - def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: - v: uint256 = convert(slice(_signature, 64, 1), uint256) - r: uint256 = convert(slice(_signature, 0, 32), uint256) - s: uint256 = convert(slice(_signature, 32, 32), uint256) - return ecrecover(_hash, v, r, s) - """ - with boa.env.prank(eth_acc.address): - mock_contract = boa.loads(src) - - permit = self.permit_class(swap)(owner=mock_contract.address, spender=bob, value=2**256 - 1, nonce=0) - sig = eth_acc.sign_message(permit.signable_message) - - res, events = call_returning_result_and_logs( - swap, - "permit", - mock_contract.address, - bob, - 2**256 - 1, - 2**256 - 1, - sig.v, - to_bytes32(sig.r), - to_bytes32(sig.s), - sender=bob, - ) - assert swap.allowance(mock_contract.address, bob) == 2**256 - 1 - assert res is True - assert len(events) == 1 - - @added_liquidity - class TestTokenTransfer: - def test_sender_balance_decreases(self, alice, bob, swap): - sender_balance = swap.balanceOf(alice) - amount = sender_balance // 4 - - swap.transfer(bob, amount, sender=alice) - - assert swap.balanceOf(alice) == sender_balance - amount - - def test_receiver_balance_increases(self, alice, bob, swap): - receiver_balance = swap.balanceOf(bob) - amount = swap.balanceOf(alice) // 4 - - swap.transfer(bob, amount, sender=alice) - - assert swap.balanceOf(bob) == receiver_balance + amount - - def test_total_supply_not_affected(self, alice, bob, swap): - total_supply = swap.totalSupply() - amount = swap.balanceOf(alice) - - swap.transfer(bob, amount, sender=alice) - - assert swap.totalSupply() == total_supply - - def test_returns_true(self, alice, bob, swap): - amount = swap.balanceOf(alice) - res = swap.transfer(bob, amount, sender=alice) - - assert res is True - - def test_transfer_full_balance(self, alice, bob, swap): - amount = swap.balanceOf(alice) - receiver_balance = swap.balanceOf(bob) - - swap.transfer(bob, amount, sender=alice) - - assert swap.balanceOf(alice) == 0 - assert swap.balanceOf(bob) == receiver_balance + amount - - def test_transfer_zero_tokens(self, alice, bob, swap): - sender_balance = swap.balanceOf(alice) - receiver_balance = swap.balanceOf(bob) - - swap.transfer(bob, 0, sender=alice) - - assert swap.balanceOf(alice) == sender_balance - assert swap.balanceOf(bob) == receiver_balance - - def test_transfer_to_self(self, alice, bob, swap): - sender_balance = swap.balanceOf(alice) - amount = sender_balance // 4 - - swap.transfer(alice, amount, sender=alice) - - assert swap.balanceOf(alice) == sender_balance - - def test_insufficient_balance(self, alice, bob, swap): - balance = swap.balanceOf(alice) - - with boa.reverts(): - swap.transfer(bob, balance + 1, sender=alice) - - def test_transfer_event_fires(self, alice, bob, swap): - amount = swap.balanceOf(alice) - _, events = call_returning_result_and_logs(swap, "transfer", bob, amount, sender=alice) - - assert len(events) == 1 - assert repr(events[0]) == f"Transfer(sender={alice}, receiver={bob}, value={amount})" - - @added_liquidity - class TestTokenTransferFrom: - def test_sender_balance_decreases(self, alice, bob, charlie, swap): - sender_balance = swap.balanceOf(alice) - amount = sender_balance // 4 - - swap.approve(bob, amount, sender=alice) - swap.transferFrom(alice, charlie, amount, sender=bob) - - assert swap.balanceOf(alice) == sender_balance - amount - - def test_receiver_balance_increases(self, alice, bob, charlie, swap): - receiver_balance = swap.balanceOf(charlie) - amount = swap.balanceOf(alice) // 4 - - swap.approve(bob, amount, sender=alice) - swap.transferFrom(alice, charlie, amount, sender=bob) - - assert swap.balanceOf(charlie) == receiver_balance + amount - - def test_caller_balance_not_affected(self, alice, bob, charlie, swap): - caller_balance = swap.balanceOf(bob) - amount = swap.balanceOf(alice) - - swap.approve(bob, amount, sender=alice) - swap.transferFrom(alice, charlie, amount, sender=bob) - - assert swap.balanceOf(bob) == caller_balance - - def test_caller_approval_affected(self, alice, bob, charlie, swap): - approval_amount = swap.balanceOf(alice) - transfer_amount = approval_amount // 4 - - swap.approve(bob, approval_amount, sender=alice) - swap.transferFrom(alice, charlie, transfer_amount, sender=bob) - - assert swap.allowance(alice, bob) == approval_amount - transfer_amount - - def test_receiver_approval_not_affected(self, alice, bob, charlie, swap): - approval_amount = swap.balanceOf(alice) - transfer_amount = approval_amount // 4 - - swap.approve(bob, approval_amount, sender=alice) - swap.approve(charlie, approval_amount, sender=alice) - swap.transferFrom(alice, charlie, transfer_amount, sender=bob) - - assert swap.allowance(alice, charlie) == approval_amount - - def test_total_supply_not_affected(self, alice, bob, charlie, swap): - total_supply = swap.totalSupply() - amount = swap.balanceOf(alice) - - swap.approve(bob, amount, sender=alice) - swap.transferFrom(alice, charlie, amount, sender=bob) - - assert swap.totalSupply() == total_supply - - def test_returns_true(self, alice, bob, charlie, swap): - amount = swap.balanceOf(alice) - swap.approve(bob, amount, sender=alice) - res = swap.transferFrom(alice, charlie, amount, sender=bob) - - assert res is True - - def test_transfer_full_balance(self, alice, bob, charlie, swap): - amount = swap.balanceOf(alice) - receiver_balance = swap.balanceOf(charlie) - - swap.approve(bob, amount, sender=alice) - swap.transferFrom(alice, charlie, amount, sender=bob) - - assert swap.balanceOf(alice) == 0 - assert swap.balanceOf(charlie) == receiver_balance + amount - - def test_transfer_zero_tokens(self, alice, bob, charlie, swap): - sender_balance = swap.balanceOf(alice) - receiver_balance = swap.balanceOf(charlie) - - swap.approve(bob, sender_balance, sender=alice) - swap.transferFrom(alice, charlie, 0, sender=bob) - - assert swap.balanceOf(alice) == sender_balance - assert swap.balanceOf(charlie) == receiver_balance - - def test_transfer_zero_tokens_without_approval(self, alice, bob, charlie, swap): - sender_balance = swap.balanceOf(alice) - receiver_balance = swap.balanceOf(charlie) - - swap.transferFrom(alice, charlie, 0, sender=bob) - - assert swap.balanceOf(alice) == sender_balance - assert swap.balanceOf(charlie) == receiver_balance - - def test_insufficient_balance(self, alice, bob, charlie, swap): - balance = swap.balanceOf(alice) - - swap.approve(bob, balance + 1, sender=alice) - with boa.reverts(): - swap.transferFrom(alice, charlie, balance + 1, sender=bob) - - def test_insufficient_approval(self, alice, bob, charlie, swap): - balance = swap.balanceOf(alice) - - swap.approve(bob, balance - 1, sender=alice) - with boa.reverts(): - swap.transferFrom(alice, charlie, balance, sender=bob) - - def test_no_approval(self, alice, bob, charlie, swap): - balance = swap.balanceOf(alice) - - with boa.reverts(): - swap.transferFrom(alice, charlie, balance, sender=bob) - - def test_revoked_approval(self, alice, bob, charlie, swap): - balance = swap.balanceOf(alice) - - swap.approve(bob, balance, sender=alice) - swap.approve(bob, 0, sender=alice) - - with boa.reverts(): - swap.transferFrom(alice, charlie, balance, sender=bob) - - def test_transfer_to_self(self, alice, bob, swap): - sender_balance = swap.balanceOf(alice) - amount = sender_balance // 4 - - swap.approve(alice, sender_balance, sender=alice) - swap.transferFrom(alice, alice, amount, sender=alice) - - assert swap.balanceOf(alice) == sender_balance - assert swap.allowance(alice, alice) == sender_balance - amount - - def test_transfer_to_self_no_approval(self, alice, bob, swap): - amount = swap.balanceOf(alice) - - with boa.reverts(): - swap.transferFrom(alice, alice, amount, sender=alice) - - def test_transfer_event_fires(self, alice, bob, charlie, swap): - amount = swap.balanceOf(alice) - - swap.approve(bob, amount, sender=alice) - _, events = call_returning_result_and_logs(swap, "transferFrom", alice, charlie, amount, sender=bob) - - assert len(events) == 1 - assert repr(events[0]) == f"Transfer(sender={alice}, receiver={charlie}, value={amount})" diff --git a/tests/token/__init__.py b/tests/token/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_get_D.py b/tests/token/test_get_D.py similarity index 100% rename from tests/test_get_D.py rename to tests/token/test_get_D.py diff --git a/tests/token/test_token_approve.py b/tests/token/test_token_approve.py new file mode 100644 index 00000000..41ea67a5 --- /dev/null +++ b/tests/token/test_token_approve.py @@ -0,0 +1,156 @@ +import boa +import pytest +from eip712 import EIP712Message +from eth_account._utils.signing import to_bytes32 + +from tests.utils.transactions import call_returning_result_and_logs + + +@pytest.mark.parametrize("idx", range(4)) +def test_initial_approval_is_zero(swap, alice, accounts, idx): + assert swap.allowance(alice, accounts[idx]) == 0 + + +def test_approve(swap, alice, bob): + swap.approve(bob, 10 ** 19, sender=alice) + assert swap.allowance(alice, bob) == 10 ** 19 + + +def test_modify_approve_zero_nonzero(swap, alice, bob): + with boa.env.prank(alice): + swap.approve(bob, 10 ** 19) + swap.approve(bob, 0) + swap.approve(bob, 12345678) + + assert swap.allowance(alice, bob) == 12345678 + + +def test_revoke_approve(swap, alice, bob): + with boa.env.prank(alice): + swap.approve(bob, 10 ** 19) + swap.approve(bob, 0) + + assert swap.allowance(alice, bob) == 0 + + +def test_approve_self(swap, alice): + swap.approve(alice, 10 ** 19, sender=alice) + assert swap.allowance(alice, alice) == 10 ** 19 + + +def test_only_affects_target(swap, alice, bob): + swap.approve(bob, 10 ** 19, sender=alice) + assert swap.allowance(bob, alice) == 0 + + +def test_returns_true(swap, alice, bob): + tx = swap.approve(bob, 10 ** 19, sender=alice) + assert tx is True + + +def test_approval_event_fires(alice, bob, swap): + value = 10 ** 19 + res, events = call_returning_result_and_logs(swap, "approve", bob, value, sender=alice) + + assert res is True + assert len(events) == 1 + assert repr(events[0]) == f"Approval(owner={alice}, spender={bob}, value={value})" + + +def test_infinite_approval(initial_setup, swap, alice, bob): + swap.approve(bob, 2 ** 256 - 1, sender=alice) + swap.transferFrom(alice, bob, 10 ** 18, sender=bob) + assert swap.allowance(alice, bob) == 2 ** 256 - 1 + + +def permit_class(swap) -> type[EIP712Message]: + class Permit(EIP712Message): + # EIP-712 Domain Fields + _name_: "string" = swap.name() # noqa: F821 + _version_: "string" = swap.version() # noqa: F821 + _chainId_: "uint256" = boa.env.chain.chain_id # noqa: F821 + _verifyingContract_: "address" = swap.address # noqa: F821 + _salt_: "bytes32" = swap.salt() # noqa: F821 + + # EIP-2612 Data Fields + owner: "address" # noqa: F821 + spender: "address" # noqa: F821 + value: "uint256" # noqa: F821 + nonce: "uint256" # noqa: F821 + deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 + + return Permit + + +def test_permit(eth_acc, bob, swap): + value = 2 ** 256 - 1 + permit = permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) + sig = eth_acc.sign_message(permit.signable_message) + + res, events = call_returning_result_and_logs( + swap, + "permit", + eth_acc.address, + bob, + 2 ** 256 - 1, + 2 ** 256 - 1, + sig.v, + to_bytes32(sig.r), + to_bytes32(sig.s), + sender=bob, + ) + + assert swap.allowance(eth_acc.address, bob) == 2 ** 256 - 1 + assert res is True + assert len(events) == 1 + assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" + assert swap.nonces(eth_acc.address) == 1 + + +def test_permit_contract(eth_acc, bob, swap): + # based on https://eips.ethereum.org/EIPS/eip-1271 + src = """ + # @version ^0.3.9 + OWNER: public(immutable(address)) + + @external + def __init__(): + OWNER = msg.sender + + @view + @external + def isValidSignature(_hash: bytes32, _signature: Bytes[65]) -> bytes32: + signer: address = self._recover_signer(_hash, _signature) + if signer == OWNER: + return 0x1626ba7e00000000000000000000000000000000000000000000000000000000 + return 0xffffffff00000000000000000000000000000000000000000000000000000000 + + @view + @internal + def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: + v: uint256 = convert(slice(_signature, 64, 1), uint256) + r: uint256 = convert(slice(_signature, 0, 32), uint256) + s: uint256 = convert(slice(_signature, 32, 32), uint256) + return ecrecover(_hash, v, r, s) + """ + with boa.env.prank(eth_acc.address): + mock_contract = boa.loads(src) + + permit = permit_class(swap)(owner=mock_contract.address, spender=bob, value=2 ** 256 - 1, nonce=0) + sig = eth_acc.sign_message(permit.signable_message) + + res, events = call_returning_result_and_logs( + swap, + "permit", + mock_contract.address, + bob, + 2 ** 256 - 1, + 2 ** 256 - 1, + sig.v, + to_bytes32(sig.r), + to_bytes32(sig.s), + sender=bob, + ) + assert swap.allowance(mock_contract.address, bob) == 2 ** 256 - 1 + assert res is True + assert len(events) == 1 diff --git a/tests/token/test_token_transfer.py b/tests/token/test_token_transfer.py new file mode 100644 index 00000000..0adecfae --- /dev/null +++ b/tests/token/test_token_transfer.py @@ -0,0 +1,86 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + + +@pytest.fixture(autouse=True) +def added_liquidity(initial_setup): ... + + +def test_sender_balance_decreases(alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance - amount + + +def test_receiver_balance_increases(alice, bob, swap): + receiver_balance = swap.balanceOf(bob) + amount = swap.balanceOf(alice) // 4 + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(bob) == receiver_balance + amount + + +def test_total_supply_not_affected(alice, bob, swap): + total_supply = swap.totalSupply() + amount = swap.balanceOf(alice) + + swap.transfer(bob, amount, sender=alice) + + assert swap.totalSupply() == total_supply + + +def test_returns_true(alice, bob, swap): + amount = swap.balanceOf(alice) + res = swap.transfer(bob, amount, sender=alice) + + assert res is True + + +def test_transfer_full_balance(alice, bob, swap): + amount = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(bob) + + swap.transfer(bob, amount, sender=alice) + + assert swap.balanceOf(alice) == 0 + assert swap.balanceOf(bob) == receiver_balance + amount + + +def test_transfer_zero_tokens(alice, bob, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(bob) + + swap.transfer(bob, 0, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(bob) == receiver_balance + + +def test_transfer_to_self(alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.transfer(alice, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + + +def test_insufficient_balance(alice, bob, swap): + balance = swap.balanceOf(alice) + + with boa.reverts(): + swap.transfer(bob, balance + 1, sender=alice) + + +def test_transfer_event_fires(alice, bob, swap): + amount = swap.balanceOf(alice) + _, events = call_returning_result_and_logs(swap, "transfer", bob, amount, sender=alice) + + assert len(events) == 1 + assert repr(events[0]) == f"Transfer(sender={alice}, receiver={bob}, value={amount})" diff --git a/tests/token/test_token_transfer_from.py b/tests/token/test_token_transfer_from.py new file mode 100644 index 00000000..e7f310b5 --- /dev/null +++ b/tests/token/test_token_transfer_from.py @@ -0,0 +1,170 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + + +@pytest.fixture(autouse=True) +def added_liquidity(initial_setup): ... + + +def test_sender_balance_decreases(alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(alice) == sender_balance - amount + + +def test_receiver_balance_increases(alice, bob, charlie, swap): + receiver_balance = swap.balanceOf(charlie) + amount = swap.balanceOf(alice) // 4 + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(charlie) == receiver_balance + amount + + +def test_caller_balance_not_affected(alice, bob, charlie, swap): + caller_balance = swap.balanceOf(bob) + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(bob) == caller_balance + + +def test_caller_approval_affected(alice, bob, charlie, swap): + approval_amount = swap.balanceOf(alice) + transfer_amount = approval_amount // 4 + + swap.approve(bob, approval_amount, sender=alice) + swap.transferFrom(alice, charlie, transfer_amount, sender=bob) + + assert swap.allowance(alice, bob) == approval_amount - transfer_amount + + +def test_receiver_approval_not_affected(alice, bob, charlie, swap): + approval_amount = swap.balanceOf(alice) + transfer_amount = approval_amount // 4 + + swap.approve(bob, approval_amount, sender=alice) + swap.approve(charlie, approval_amount, sender=alice) + swap.transferFrom(alice, charlie, transfer_amount, sender=bob) + + assert swap.allowance(alice, charlie) == approval_amount + + +def test_total_supply_not_affected(alice, bob, charlie, swap): + total_supply = swap.totalSupply() + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.totalSupply() == total_supply + + +def test_returns_true(alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + swap.approve(bob, amount, sender=alice) + res = swap.transferFrom(alice, charlie, amount, sender=bob) + + assert res is True + + +def test_transfer_full_balance(alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.approve(bob, amount, sender=alice) + swap.transferFrom(alice, charlie, amount, sender=bob) + + assert swap.balanceOf(alice) == 0 + assert swap.balanceOf(charlie) == receiver_balance + amount + + +def test_transfer_zero_tokens(alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.approve(bob, sender_balance, sender=alice) + swap.transferFrom(alice, charlie, 0, sender=bob) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(charlie) == receiver_balance + + +def test_transfer_zero_tokens_without_approval(alice, bob, charlie, swap): + sender_balance = swap.balanceOf(alice) + receiver_balance = swap.balanceOf(charlie) + + swap.transferFrom(alice, charlie, 0, sender=bob) + + assert swap.balanceOf(alice) == sender_balance + assert swap.balanceOf(charlie) == receiver_balance + + +def test_insufficient_balance(alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance + 1, sender=alice) + with boa.reverts(): + swap.transferFrom(alice, charlie, balance + 1, sender=bob) + + +def test_insufficient_approval(alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance - 1, sender=alice) + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + +def test_no_approval(alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + +def test_revoked_approval(alice, bob, charlie, swap): + balance = swap.balanceOf(alice) + + swap.approve(bob, balance, sender=alice) + swap.approve(bob, 0, sender=alice) + + with boa.reverts(): + swap.transferFrom(alice, charlie, balance, sender=bob) + + +def test_transfer_to_self(alice, bob, swap): + sender_balance = swap.balanceOf(alice) + amount = sender_balance // 4 + + swap.approve(alice, sender_balance, sender=alice) + swap.transferFrom(alice, alice, amount, sender=alice) + + assert swap.balanceOf(alice) == sender_balance + assert swap.allowance(alice, alice) == sender_balance - amount + + +def test_transfer_to_self_no_approval(alice, bob, swap): + amount = swap.balanceOf(alice) + + with boa.reverts(): + swap.transferFrom(alice, alice, amount, sender=alice) + + +def test_transfer_event_fires(alice, bob, charlie, swap): + amount = swap.balanceOf(alice) + + swap.approve(bob, amount, sender=alice) + _, events = call_returning_result_and_logs(swap, "transferFrom", alice, charlie, amount, sender=bob) + + assert len(events) == 1 + assert repr(events[0]) == f"Transfer(sender={alice}, receiver={charlie}, value={amount})" From 35571d2cd1dcfd4d4f21d4ce94ec0f9aca4ee185 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 12 Jan 2024 10:03:37 +0100 Subject: [PATCH 286/337] More fixtures in function scope --- tests/fixtures/constants.py | 4 ++-- tests/fixtures/factory.py | 24 ++++++++++++------------ tests/pools/test_erc4626_swaps.py | 5 ++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index f7d3d228..720eb36b 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -8,7 +8,7 @@ def initial_balance() -> int: return INITIAL_AMOUNT * 10**18 -@pytest.fixture(scope="module") +@pytest.fixture() def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: return ( [INITIAL_AMOUNT * 10**precision for precision in decimals] @@ -17,7 +17,7 @@ def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: ) -@pytest.fixture(scope="module") +@pytest.fixture() def deposit_amounts( initial_amounts: list[int], pool_type, pool_token_types, metapool_token_type, pool_tokens, underlying_tokens ) -> list[int]: diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index e6d8896f..8b38646e 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -2,54 +2,54 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture() def gauge_interface(): return boa.load_partial("contracts/main/LiquidityGauge.vy") -@pytest.fixture(scope="module") +@pytest.fixture() def gauge_implementation(deployer, gauge_interface): with boa.env.prank(deployer): return gauge_interface.deploy_as_blueprint() -@pytest.fixture(scope="module") +@pytest.fixture() def amm_interface(): return boa.load_partial("contracts/main/CurveStableSwapNG.vy") -@pytest.fixture(scope="module") +@pytest.fixture() def amm_implementation(deployer, amm_interface): with boa.env.prank(deployer): impl = amm_interface.deploy_as_blueprint() return impl -@pytest.fixture(scope="module") +@pytest.fixture() def amm_interface_meta(): return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") -@pytest.fixture(scope="module") +@pytest.fixture() def amm_implementation_meta(deployer, amm_interface_meta): with boa.env.prank(deployer): impl = amm_interface_meta.deploy_as_blueprint() return impl -@pytest.fixture(scope="module") +@pytest.fixture() def views_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGViews.vy") -@pytest.fixture(scope="module") +@pytest.fixture() def math_implementation(deployer): with boa.env.prank(deployer): return boa.load("contracts/main/CurveStableSwapNGMath.vy") -@pytest.fixture(scope="module") +@pytest.fixture() def factory( deployer, fee_receiver, @@ -103,19 +103,19 @@ def add_base_pool( ) -@pytest.fixture(scope="module") +@pytest.fixture() def set_gauge_implementation(owner, factory, gauge_implementation): with boa.env.prank(owner): factory.set_gauge_implementation(gauge_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture() def set_views_implementation(owner, factory, views_implementation): with boa.env.prank(owner): factory.set_views_implementation(views_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture() def set_math_implementation(owner, factory, math_implementation): with boa.env.prank(owner): factory.set_math_implementation(math_implementation.address) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 47467fe5..99de923a 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -112,7 +112,7 @@ def asset_types(pool_tokens): return _asset_types -@pytest.fixture(scope="module") +@pytest.fixture() def empty_swap( deployer, factory, @@ -176,9 +176,8 @@ def deposit_amounts(pool_erc20_tokens, token_a, bob): return _deposit_amounts -@pytest.fixture(scope="module") +@pytest.fixture() def swap(empty_swap, bob, deposit_amounts, pool_tokens): - for token in pool_tokens: token.approve(empty_swap, 2**256 - 1, sender=bob) empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) From f2cbfc22494ba8ffd4a435e2727c648ea2ace44b Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 15 Jan 2024 15:23:24 +0100 Subject: [PATCH 287/337] Deployers in session scope + fixture fixes --- .pre-commit-config.yaml | 24 +- poetry.lock | 1975 ++++++++--------- pyproject.toml | 2 +- tests/conftest.py | 24 +- tests/constants.py | 3 + tests/factory/test_factory_add_pools.py | 79 +- tests/factory/test_factory_general.py | 2 +- tests/fixtures/factory.py | 89 +- tests/fixtures/pools.py | 80 +- tests/gauge/test_rewards.py | 6 +- tests/pools/meta/test_meta_new_ng_base.py | 49 +- tests/pools/meta/test_meta_zap.py | 35 +- tests/pools/test_erc4626_swaps.py | 88 +- .../test_specific_liquidity_operations.py | 39 +- tests/token/test_token_approve.py | 44 +- 15 files changed, 1191 insertions(+), 1348 deletions(-) create mode 100644 tests/constants.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 421b2beb..13b11e6a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,26 +2,28 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - - repo: https://github.com/psf/black - rev: 22.3.0 + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.12.0 hooks: - id: black - args: [--line-length=120] - - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 + args: + - --skip-magic-trailing-comma + - --target-version=py310 + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 hooks: - id: flake8 - args: [--max-line-length=120] - - repo: https://github.com/pycqa/isort - rev: 5.12.0 + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 hooks: - id: isort - args: ["--profile", "black", --line-length=120] + # profile and line-length to avoid clashes with black + args: ["--profile=black", "--line-length=88"] default_language_version: - python: python3.10.4 + python: python3.10 diff --git a/poetry.lock b/poetry.lock index 56d0244a..d9d472db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,191 +1,171 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "appnope" -version = "0.1.3" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = "*" -files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "asttokens" -version = "2.4.0" +version = "2.4.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"}, - {file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"}, + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] [package.dependencies] six = ">=1.12.0" [package.extras] -test = ["astroid", "pytest"] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] - -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -optional = false -python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "bitarray" -version = "2.8.2" +version = "2.9.2" description = "efficient arrays of booleans -- C extension" optional = false python-versions = "*" files = [ - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525eda30469522cd840a11ba866d0616c132f6c4be8966a297d7545e97fcb822"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3d9730341c825eb167ca06c9dddf6ad4d1b4e71ea7da73cc8c5139fcb5e14ca"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad8f8c39c8df184e346184699783f105755003662f0dbe1233d9d9849650ab5f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8d08330d250df47088c13683322083afbdfafdc31df205616506d6b9f068f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f19ccba8a6ddf1382b0fb4fb8d4e1330e4a1b148e5d198f0981ba2a97c3492"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4db2e0f58153a376d9a14873e342d507ca32640640284cddf3c1e74a65929477"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b3c27aeea1752f0c1df1e29115e4b6f0249173d71e53c5f7e2c821706f028b"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef23f62b3abd287cf368341540ef2a81c86b48de9d488e182e63fe24ac165538"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d79fd3c58a4dc71ffd0fc55982a9a2079fe94c76ccff2777092f6107d6a049a"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8528c59d3d3df6618777892b60435022d8917de9ea32933d439c7ffd24437237"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c35bb5fe018fd9c42be3c28e74dc7dcfae471c3c6689679dbd0bd1d6dc0f51b7"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:232e8faa8e624f3eb0552a636ebe745cee00480e0e56ad62f17808d281838f2e"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:945e97ad2bbf7885426f39641a735a31fd4ca2e84e4d0cd271d9880372d6eae1"}, - {file = "bitarray-2.8.2-cp310-cp310-win32.whl", hash = "sha256:88c2d427ab1b20f220c1d53171b0691faa8f0a219367d84e859f1001e90ceefc"}, - {file = "bitarray-2.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:f7c5745e0f96c2c16c03c7540dbe26f3b62ddee63059be0a014156933f054024"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a610426251d1340baa4d8b7942d2cbfe6a1e20b92c66817ab582e0d341185ab5"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599b04b04eb1b5b964a35986bea2bc4381145836fe550cc33c40a796b855b985"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9014660472f2080d550157164cc5f9376245a34a0ab877b82b95c1f894af5b28"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:532d63c54159f7e0fb520e2f72ef596493bc43810eaa75fac7a188e898ab593b"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1563f11dd70cb1684cfe841e4cf7f35d4f65769de21d12b72cf773a7932615"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e456150af62ee1f24a0c9976947629bfb80d80b4fbd37aa901cf794db6ba9b0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc29909e4cef05d5e49f5d77ace1dc49311c7791734a048b690521c76b4b7a0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:608385f07a4b0391d4982d1efb83ad70920cd8ca495a7868e44d2a4511cbf84e"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2baf7ec353fa64917045b3efe26e7c12ce0d7b4d120c3773a612dce54f91585"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2c39d1cb04fc277701de6fe2119cc71facc4aff2ca0414b2e326aec337fa1ab4"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:3caf4ca668854bb23db4b65af0068238677b5791bcc45694bf8990f3e26e85c9"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4bbfe4474d3470c724e283bd1fe8ee9ab3cb6a4c378112926f45d41e326a7622"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb941981676dc7859d53199a10a33ca56a3146cce6a45bc6ad70572c1147157d"}, - {file = "bitarray-2.8.2-cp311-cp311-win32.whl", hash = "sha256:e8963d7ac292f41654fa7cbc1a34efdb09e5a42399b2e3689c3fd5b8b4e0fe16"}, - {file = "bitarray-2.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:ee779a291330287b341044635fce2979176d113b0dcce0308dc5d62da7951eec"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:05d84765bbfd0aa10890c765c56c917c237987325c4e327f3c0febbfc34365c8"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c7b7be4bff27d7ce6a81d3993755371b5f5b42436afa151868e8fd599acbab19"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c3d51ab9f3d5b9a10295abe480c50bf74ee5bf3d984c4cee77e493e575acc869"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00bad63ef6f9d22ba36b01b89167176a451ea22a916d1dfa77d73e0298f1d1f9"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:225e19d37b234d4d721557434b7d5590cd63b6342492b689e2d694d44d7cc537"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e3ab9870c496e5a058436bf4d96ed111ca6154c8ef8147b70c44c188d6fb2c"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3e182c766cd6f302e99e0d8e44927d533356e9d6ac93fcd09987ebead467aa"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7bb559b68eb9cb3c4f867eb9fb39a696c4da70a41fad37b410bd0c7b426a8ce"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:97e658a3793478d6bca684f47f29f62542312683687bc045dc3cb588160e74b3"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:dd351b8fbc77c2e2ebc3eeadc0cf72bd5024a43bef5a847697e2b076d1201636"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:280809e56a7098f48165ce134222098e4cfe7084b10d69bbc31367942e541dfd"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14bc38ced7edffff25ee748c1eabc530624c9af68f86322b030b11b7918b966f"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de4953b6b1e19dabd23767bd1f83f1cf73978372189dec0e2dd8b3d6971100d6"}, - {file = "bitarray-2.8.2-cp312-cp312-win32.whl", hash = "sha256:99196b4730d887a4bc578f05039b55dc57b131c81b5a5e03efa619b587bdf293"}, - {file = "bitarray-2.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:215a5bf8fdcbed700cc8782d4044e1f036606d5c321710d83e8da6d0fdfe07d5"}, - {file = "bitarray-2.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9c54136c9fab2cefe9801e336b8a3aa7299bcfe7f387379cc6394ad1d5a484b"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08ad70c1555d9622cecd8f1b132a5341d183a9161aba93cc9739bbaabe4220b0"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:384be6b7df8fb6a93ddd88d4184094f2ba4f1d07c30dcd4ae164d185d31a2af6"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd2a098250c683d248a6490ac437ed56f7164d2151572231bd26c76bfe111b11"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ae5c18b9a70cb0ae576a8a3c8a9a0659356c016b49cc6b263dd987d344f30d"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:188f5780f1cfbeba0c3ddb1aa3fa0415ab1a8aa04e9e89f70ad5403197013437"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5f2a96c5b40727bc21a695d3a106f49e88572fa11427bf2193cabd99e624c901"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b6df948da34b5fb949698092573d798c76c54f2f2188db59276d599075f9ed04"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f00c328b8dae1828844bac019dfe425d10a2043cc70e2f967224c5392d19ad"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7965108069f9731306a882872c23ad4f5a8531668e82b27932a19814c52a8dd8"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:420aa610fe392c4ee700e474673276bb4f3c4f091d001f58b1f018bf650840c1"}, - {file = "bitarray-2.8.2-cp36-cp36m-win32.whl", hash = "sha256:b85929db81105c06e8292c05cac093068e86464555c628c03f99c9f8090d68d4"}, - {file = "bitarray-2.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:cba09dfd3aea2addc994eb21a861c3cea2d68141bb7ebe68b0e94c73405540f9"}, - {file = "bitarray-2.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:172169099797f1ec469b0aadb00c653193a74757f99312c9c17dc1a18d23d972"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a4fed240728dcc96966e0c4cfd3dce870525377a1cb5afac8e5cfe116ff7b"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff31bef13fd278446b6d1969a46db9f02c36fd905f3e75878f0fe17271f7d897"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb8b727cd9ddff848c5f73e65470abb110f026beab403bcebbd74e7439b9bd8f"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1356c86eefbde3fe8a3c39fb81bbc8b16acc8e442e191408042e8b1d6904e3"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7706336bd15acf4e42300579e42bef742c01a4eb202998f6c20c443a2ce5fd60"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a4b43949477dc2b0d3e1d8b7c413ed74f515cef01954cdcc3fb1e2dcc49f2aff"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:06d9de5db244c6e45a5318713367765de0a57d82ad616869a004a710a95541e9"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5569c8314335e92570c471d60b4b03eb2a4467864805a560d133d24b27b3961a"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:76a4faef4c31953aa7b9ebe00d162f7ce9bc03fc8d423ab2dc690a11d7520a8e"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1474db8c4297026e1daa1699e70e25e56dff91104fe025b1a9804332f2737604"}, - {file = "bitarray-2.8.2-cp37-cp37m-win32.whl", hash = "sha256:85b504f233f0484e9a74df4f286a9ae56fbbe2a648c45726761cf7b6f072cdc8"}, - {file = "bitarray-2.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3dde123ce85d1ba99d9bdf44b1b3174fa22bc8fb10004e0d72bb661a0444c1a9"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23fae6a5a1403d16592b8823d5dea93f738c6e217a1e1bb0eefad242fb03d47f"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c44b3022115eb1697315bc51aeadbade1a19d7188bcda66c52d91209cf2963ca"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fea9354b7169810e2bdd6f3265ff128b564a25d38479b9ad0a9c5776e4fd0cfc"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f699bf2cb223aeec04a106003bd2bf8a4fc6d4c5eddf79cacecb6b267657ac5"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:462c9425fbc5315cbc20a72ca62558e5545bb0f6dc9355e2fa96fa747e9b1a80"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c8716b4c45fb128cd4da143749e276f150ecb0acb711f4969d7e7ebc9b2a675"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79fde5b27e35aedd958f5fb58ebabce47d7eddae5a5e3774088c30c9610195ef"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abf2593b91e36f1cb1c40ac895993c7d2eb30d3f1cb0954a80e5f13697b6b69"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ab2e03dd140ab93b91f94a785d1cd6082d5ab53ab6ec958726efa0ad17f7b87a"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9e895cc3e5ffee269dd9866097e227a68022ef2b78d627a6ed737534d0c88c14"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0bbeb7120ec1a9b26ce423e74cad7b414cea9e35f8e05599e3b3dceb87f4d1b6"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51d45d56be14b69720d11a8c61e101d86a65dc8a3a9f356bbe4d98cf4f3c5617"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:726a598e34657772e5f131115741ea8709e9b55fa35d63c4717bc16b2a737d38"}, - {file = "bitarray-2.8.2-cp38-cp38-win32.whl", hash = "sha256:ab87c4c50d65932788d058adbbd28a209144523ffacbab81dd41582ffce26af9"}, - {file = "bitarray-2.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:316147fb62c810a7667277e5ae7bb75b2871c32d2c398aeb4503cbd4cf3315e7"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36bdde1aba78e4a3a6ce5cbebd0a6bc967b0c3fbd8bd99a197dcc17d654f423c"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:932f7b77750dff7140522dc97dfd94533a599ef1c5d0be3733f556fd44a68821"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5819b95d0ccce864066f062d2329363ae8a64b9c3d076d039c75ffc9204c2a12"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c28b52e59a5e6aa00a929b35b04473bd479a74237ab1170c573c49e8aca61fe"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ecdd528268478efeb78ed0132b01104bda6cd8f10c8a57708fc87b1add77e4d"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f6f245d4a5e707d48274f38551b654a36db4fb83437c98be00d2019263aa364"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b088f06d9e2f523683ae363e227173ac454dbb56c938c6d42791fdd78bad8da7"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e883919cea8e446c5c49717a7ce5c93a016a02b9429b81d64b9ab1d80fc12e42"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:09d729420b8edc4d8a23a518ae4553074a0054d0441c1a461b425c2f033fab5e"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d0d0923087fe1f2d85daa68463d221e90b4b8ed0356480c887eea90b2a2cc7ee"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:70cebcf9bc345ac1e034fa781eac3619323eaf87f7bbe26f0e28850beb6f5634"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:890355bf6ba3dc04b5a23d1328eb1f6062165e6262197cebc9acfebdcb23144c"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f0b54b95e39036c116ffc057b3f56f6084ce88822de3d5d1f57fa38554ccf5c1"}, - {file = "bitarray-2.8.2-cp39-cp39-win32.whl", hash = "sha256:b499d93fa31a73e31ee62f2cbe07e4df833fd7151734b8f07c48ffe3e4547ec5"}, - {file = "bitarray-2.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:b007aaf5810c708c5a2778e371aa546d7084e4e9f82f65865b2ce5a182376f42"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b734b074a09b1b2e1de7df423565412d9213faefa8ca422f32be756b189f729"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd074b06be9484040acb4c2c0462c4d19a43e377716be7ba10440f51a57bb98c"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678696bb613f0344b79be385747aae705b327a9a32ace45a353dd16497bc719"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb337ffa10824fa2025c4b1c06a2d809dbed4a4bf9e3ffb262676d084c4e0c50"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2b3c7aa2c9a6533dc7234d2a303efdcb9df3f4ac4d0919ec1caf568868f12a0a"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6765c47b487341837b3731cca3c8033b971ee082f6ab41cb430aa3447686eec"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8566b535bc4ebb26247d6f636a27bb0038bc93fa7e55121628f5cd6b0906ac"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56764825f64ab983d32b8c1d4ee483f415f2559e59388ba266a9fcafc44305bf"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f45f7d58c399e90ee3bddff4f3e2f53ff95c948b2d43de304266153ebd1d778"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:095851409e0db75b1416c8c3e24957135d5a2a206790578e43739e92a00c17c4"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8bb60d5a948f00901da1d7e4953189259b3c7ef79391fecd6f18db3f48a036fe"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2dc483ada55ef35990b67dc0e7a779f0b2ce79d156e452dc8b835b03c0dca9"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a35e308c23f039064600108fc1c8416bd102bc3cf3a6915761a9f7c801237e0"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa49f6cfcae4305d8cff028dc9c9a881189a38f7ca43c085aef894c58cb6fbde"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:111bf9913ebee4630e2cb43b61d0abb39813b231262b114e5268cd6a405a22b9"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b71d82e3f001bcb53463023f7f37e223fff56cf048f577c6d85597db94770f10"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:440c537fdf2eaee7fdd41fb1dce5701c490c1964fdb74225b10b49a7c45bc7b4"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c384c49ce52b82d5b0355000b8aeb7e3a7654997916c1e6fd9d29697edda1076"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27428d7b0e706307d0c697f81599e7af4f52e5873ea6bc269eae3604b16b81fe"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4963982d5da0825768f9a80760a8560c3e4cf711a9a7ea06ff9bcb7bd250b131"}, - {file = "bitarray-2.8.2.tar.gz", hash = "sha256:f90b2f44b5b23364d5fbade2c34652e15b1fcfe813c46f828e008f68a709160f"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, ] [[package]] @@ -289,62 +269,64 @@ files = [ [[package]] name = "cbor2" -version = "5.4.6" +version = "5.5.1" description = "CBOR (de)serializer with extensive tag support" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "cbor2-5.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:309fffbb7f561d67f02095d4b9657b73c9220558701c997e9bfcfbca2696e927"}, - {file = "cbor2-5.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff95b33e5482313a74648ca3620c9328e9f30ecfa034df040b828e476597d352"}, - {file = "cbor2-5.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9eb582fce972f0fa429d8159b7891ff8deccb7affc4995090afc61ce0d328a"}, - {file = "cbor2-5.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3950be57a1698086cf26d8710b4e5a637b65133c5b1f9eec23967d4089d8cfed"}, - {file = "cbor2-5.4.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:78304df140b9e13b93bcbb2aecee64c9aaa9f1cadbd45f043b5e7b93cc2f21a2"}, - {file = "cbor2-5.4.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e73ca40dd3c7210ff776acff9869ddc9ff67bae7c425b58e5715dcf55275163f"}, - {file = "cbor2-5.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:0b956f19e93ba3180c336282cd1b6665631f2d3a196a9c19b29a833bf979e7a4"}, - {file = "cbor2-5.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c12c0ab78f5bc290b08a79152a8621822415836a86f8f4b50dadba371736fda"}, - {file = "cbor2-5.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3545b16f9f0d5f34d4c99052829c3726020a07be34c99c250d0df87418f02954"}, - {file = "cbor2-5.4.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24144822f8d2b0156f4cda9427f071f969c18683ffed39663dc86bc0a75ae4dd"}, - {file = "cbor2-5.4.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1835536e76ea16e88c934aac5e369ba9f93d495b01e5fa2d93f0b4986b89146d"}, - {file = "cbor2-5.4.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:39452c799453f5bf33281ffc0752c620b8bfa0b7c13070b87d370257a1311976"}, - {file = "cbor2-5.4.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3316f09a77af85e7772ecfdd693b0f450678a60b1aee641bac319289757e3fa0"}, - {file = "cbor2-5.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:456cdff668a50a52fdb8aa6d0742511e43ed46d6a5b463dba80a5a720fa0d320"}, - {file = "cbor2-5.4.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9394ca49ecdf0957924e45d09a4026482d184a465a047f60c4044eb464c43de9"}, - {file = "cbor2-5.4.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dfa030cd3d67e5b6701d3067923f2f61536a8ffb1b45be14775d1e866b59ae"}, - {file = "cbor2-5.4.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5094562dfe3e5583202b93ef7ca5082c2ba5571accb2c4412d27b7d0ba8a563"}, - {file = "cbor2-5.4.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:94f844d0e232aca061a86dd6ff191e47ba0389ddd34acb784ad9a41594dc99a4"}, - {file = "cbor2-5.4.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7bbd3470eb685325398023e335be896b74f61b014896604ed45049a7b7b6d8ac"}, - {file = "cbor2-5.4.6-cp37-cp37m-win_amd64.whl", hash = "sha256:0bd12c54a48949d11f5ffc2fa27f5df1b4754111f5207453e5fae3512ebb3cab"}, - {file = "cbor2-5.4.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2984a488f350aee1d54fa9cb8c6a3c1f1f5b268abbc91161e47185de4d829f3"}, - {file = "cbor2-5.4.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c285a2cb2c04004bfead93df89d92a0cef1874ad337d0cb5ea53c2c31e97bfdb"}, - {file = "cbor2-5.4.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6709d97695205cd08255363b54afa035306d5302b7b5e38308c8ff5a47e60f2a"}, - {file = "cbor2-5.4.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96087fa5336ebfc94465c0768cd5de0fcf9af3840d2cf0ce32f5767855f1a293"}, - {file = "cbor2-5.4.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0d2b926b024d3a1549b819bc82fdc387062bbd977b0299dd5fa5e0ea3267b98b"}, - {file = "cbor2-5.4.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6e1b5aee920b6a2f737aa12e2b54de3826b09f885a7ce402db84216343368140"}, - {file = "cbor2-5.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:79e048e623846d60d735bb350263e8fdd36cb6195d7f1a2b57eacd573d9c0b33"}, - {file = "cbor2-5.4.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80ac8ba450c7a41c5afe5f7e503d3092442ed75393e1de162b0bf0d97edf7c7f"}, - {file = "cbor2-5.4.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ce1a2c272ba8523a55ea2f1d66e3464e89fa0e37c9a3d786a919fe64e68dbd7"}, - {file = "cbor2-5.4.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1618d16e310f7ffed141762b0ff5d8bb6b53ad449406115cc465bf04213cefcf"}, - {file = "cbor2-5.4.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbbdb2e3ef274865dc3f279aae109b5d94f4654aea3c72c479fb37e4a1e7ed7"}, - {file = "cbor2-5.4.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f9c702bee2954fffdfa3de95a5af1a6b1c5f155e39490353d5654d83bb05bb9"}, - {file = "cbor2-5.4.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b9f3924da0e460a93b3674c7e71020dd6c9e9f17400a34e52a88c0af2dcd2aa"}, - {file = "cbor2-5.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:d54bd840b4fe34f097b8665fc0692c7dd175349e53976be6c5de4433b970daa4"}, - {file = "cbor2-5.4.6-py3-none-any.whl", hash = "sha256:181ac494091d1f9c5bb373cd85514ce1eb967a8cf3ec298e8dfa8878aa823956"}, - {file = "cbor2-5.4.6.tar.gz", hash = "sha256:b893500db0fe033e570c3adc956af6eefc57e280026bd2d86fd53da9f1e594d7"}, -] - -[package.extras] -doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["pytest", "pytest-cov"] + {file = "cbor2-5.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:37ba4f719384bd4ea317e92a8763ea343e205f3112c8241778fd9dbc64ae1498"}, + {file = "cbor2-5.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:425ae919120b9d05b4794b3e5faf6584fc47a9d61db059d4f00ce16ae93a3f63"}, + {file = "cbor2-5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c511ff6356d6f4292ced856d5048a24ee61a85634816f29dadf1f089e8cb4f9"}, + {file = "cbor2-5.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6ab54a9282dd99a3a70d0f64706d3b3592e7920564a93101caa74dec322346c"}, + {file = "cbor2-5.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:39d94852dd61bda5b3d2bfe74e7b194a7199937d270f90099beec3e7584f0c9b"}, + {file = "cbor2-5.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65532ba929beebe1c63317ad00c79d4936b60a5c29a3c329d2aa7df4e72ad907"}, + {file = "cbor2-5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:1206180f66a9ad23e692cf457610c877f186ad303a1264b6c5335015b7bee83e"}, + {file = "cbor2-5.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:42155a20be46312fad2ceb85a408e2d90da059c2d36a65e0b99abca57c5357fd"}, + {file = "cbor2-5.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f3827ae14c009df9b37790f1da5cd1f9d64f7ffec472a49ebf865c0af6b77e9"}, + {file = "cbor2-5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bfa417dbb8b4581ad3c2312469899518596551cfb0fe5bdaf8a6921cff69d7e"}, + {file = "cbor2-5.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3317e7dfb4f3180be90bcd853204558d89f119b624c2168153b53dea305e79d"}, + {file = "cbor2-5.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a5770bdf4340de55679efe6c38fc6d64529fda547e7a85eb0217a82717a8235"}, + {file = "cbor2-5.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b5d53826ad0c92fcb004b2a475896610b51e0ca010f6c37d762aae44ab0807b2"}, + {file = "cbor2-5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc77cac985f7f7a20f2d8b1957d1e79393d7df823f61c7c6173d3a0011c1d770"}, + {file = "cbor2-5.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9e45d5aa8e484b4bf57240d8e7949389f1c9d4073758abb30954386321b55c9d"}, + {file = "cbor2-5.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93b949a66bec40dd0ca87a6d026136fea2cf1660120f921199a47ac8027af253"}, + {file = "cbor2-5.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93d601ca92d917f769370a5e6c3ead62dca6451b2b603915e4fcf300083b9fcd"}, + {file = "cbor2-5.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11876abd50b9f70d114fcdbb0b5a3249ccd7d321465f0350028fd6d2317e114"}, + {file = "cbor2-5.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fd77c558decdba2a2a7a463e6346d53781d2163bacf205f77b999f561ba4ac73"}, + {file = "cbor2-5.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efb81920d80410b8e80a4a6a8b06ec9b766be0ae7f3029af8ae4b30914edcfa3"}, + {file = "cbor2-5.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:4bb35f3b1ebd4b7b37628f0cd5c839f3008dec669194a2a4a33d91bab7f8663b"}, + {file = "cbor2-5.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f41e4a439f642954ed728dc18915098b5f2ebec7029eaebe52c06c52b6a9a63a"}, + {file = "cbor2-5.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4eae4d56314f22920a28bf7affefdfc918646877ce3b16220dc6cf38a584aa41"}, + {file = "cbor2-5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559a0c1ec8dcedd6142b81727403e0f5a2e8f4c18e8bb3c548107ec39af4e9cb"}, + {file = "cbor2-5.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537da7bfee97ee44a11b300c034c18e674af6a5dc4718a6fba141037f099c7ec"}, + {file = "cbor2-5.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5c99fd8bbc6bbf3bf4d6b2996594ae633b778b27b0531559487950762c4e1e3f"}, + {file = "cbor2-5.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ee46e6dbc8e2cf302a022fec513d57dba65e9d5ec495bcd1ad97a5dbdbab249"}, + {file = "cbor2-5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:67e2be461320197495fff55f250b111d4125a0a2d02e6256e41f8598adc3ad3f"}, + {file = "cbor2-5.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4384a56afef0b908b61c8ea3cca3e257a316427ace3411308f51ee301b23adf9"}, + {file = "cbor2-5.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8cc64acc606b7f2a4b673a1d6cde5a9cb1860a6ce27b353e269c9535efbd62c"}, + {file = "cbor2-5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50019fea3cb07fa9b2b53772a52b4243e87de232591570c4c272b3ebdb419493"}, + {file = "cbor2-5.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a18be0af9241883bc67a036c1f33e3f9956d31337ccd412194bf759bc1095e03"}, + {file = "cbor2-5.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:60e7e0073291096605de27de3ce006148cf9a095199160439555f14f93d044d5"}, + {file = "cbor2-5.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41f7501338228b27dac88c1197928cf8985f6fc775f59be89c6fdaddb4e69658"}, + {file = "cbor2-5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c85ab7697252af2240e939707c935ea18081ccb580d4b5b9a94b04148ab2c32b"}, + {file = "cbor2-5.5.1-py3-none-any.whl", hash = "sha256:dca639c8ff81b9f0c92faf97324adfdbfb5c2a5bb97f249606c6f5b94c77cc0d"}, + {file = "cbor2-5.5.1.tar.gz", hash = "sha256:f9e192f461a9f8f6082df28c035b006d153904213dc8640bed8a72d72bbc9475"}, +] + +[package.extras] +benchmarks = ["pytest-benchmark (==4.0.0)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"] +test = ["coverage (>=7)", "hypothesis", "pytest"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -424,117 +406,117 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "cleo" -version = "2.0.1" +version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "cleo-2.0.1-py3-none-any.whl", hash = "sha256:6eb133670a3ed1f3b052d53789017b6e50fca66d1287e6e6696285f4cb8ea448"}, - {file = "cleo-2.0.1.tar.gz", hash = "sha256:eb4b2e1f3063c11085cebe489a6e9124163c226575a3c3be69b2e51af4a15ec5"}, + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, ] [package.dependencies] crashtest = ">=0.4.1,<0.5.0" -rapidfuzz = ">=2.2.0,<3.0.0" +rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "click" @@ -561,20 +543,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -optional = false -python-versions = "*" -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - [[package]] name = "crashtest" version = "0.4.1" @@ -588,34 +556,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.4" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -763,78 +731,91 @@ files = [ [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] name = "dulwich" -version = "0.21.6" +version = "0.21.7" description = "Python Git Library" optional = false python-versions = ">=3.7" files = [ - {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f89bee4c97372e8aaf8ffaf5899f1bcd5184b5306d7eaf68738c1101ceba10e"}, - {file = "dulwich-0.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:847bb52562a211b596453a602e75739350c86d7edb846b5b1c46896a5c86b9bb"}, - {file = "dulwich-0.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e09d0b4e985b371aa6728773781b19298d361a00772e20f98522868cf7edc6f"}, - {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfb50b3915e223a97f50fbac0dbc298d5fffeaac004eeeb3d552c57fe38416f"}, - {file = "dulwich-0.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a64eca1601e79c16df78afe08da9ac9497b934cbc5765990ca7d89a4b87453d9"}, - {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fedd924763a5d640348db43a267a394aa80d551228ad45708e0b0cc2130bb62"}, - {file = "dulwich-0.21.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edc21c3784dd9d9b85abd9fe53f81a884e2cdcc4e5e09ada17287420d64cfd46"}, - {file = "dulwich-0.21.6-cp310-cp310-win32.whl", hash = "sha256:daa3584beabfcf0da76df57535a23c80ff6d8ccde6ddbd23bdc79d317a0e20a7"}, - {file = "dulwich-0.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:40623cc39a3f1634663d22d87f86e2e406cc8ff17ae7a3edc7fcf963c288992f"}, - {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ed878553f0b76facbb620b455fafa0943162fe8e386920717781e490444efa"}, - {file = "dulwich-0.21.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a89b19f4960e759915dbc23a4dd0abc067b55d8d65e9df50961b73091b87b81a"}, - {file = "dulwich-0.21.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28acbd08d6b38720d99cc01da9dd307a2e0585e00436c95bcac6357b9a9a6f76"}, - {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2f2683e0598f7c7071ef08a0822f062d8744549a0d45f2c156741033b7e3d7d"}, - {file = "dulwich-0.21.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54342cf96fe8a44648505c65f23d18889595762003a168d67d7263df66143bd2"}, - {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a3fc071e5b14f164191286f7ffc02f60fe8b439d01fad0832697cc08c2237dd"}, - {file = "dulwich-0.21.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32d7acfe3fe2ce4502446d8f7a5ab34cfd24c9ff8961e60337638410906a8fbb"}, - {file = "dulwich-0.21.6-cp311-cp311-win32.whl", hash = "sha256:5e58171a5d70f7910f73d25ff82a058edff09a4c1c3bd1de0dc6b1fbc9a42c3e"}, - {file = "dulwich-0.21.6-cp311-cp311-win_amd64.whl", hash = "sha256:ceabe8f96edfb9183034a860f5dc77586700b517457032867b64a03c44e5cf96"}, - {file = "dulwich-0.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4fdc2f081bc3e9e120079c2cea4be213e3f127335aca7c0ab0c19fe791270caa"}, - {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe957564108f74325d0d042d85e0c67ef470921ca92b6e7d330c7c49a3b9c1d"}, - {file = "dulwich-0.21.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2912c8a845c8ccbc79d068a89db7172e355adeb84eb31f062cd3a406d528b30"}, - {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:81e237a6b1b20c79ef62ca19a8fb231f5519bab874b9a1c2acf9c05edcabd600"}, - {file = "dulwich-0.21.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:513d045e74307eeb31592255c38f37042c9aa68ce845a167943018ab5138b0e3"}, - {file = "dulwich-0.21.6-cp37-cp37m-win32.whl", hash = "sha256:e1ac882afa890ef993b8502647e6c6d2b3977ce56e3fe80058ce64607cbc7107"}, - {file = "dulwich-0.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:5d2ccf3d355850674f75655154a6519bf1f1664176c670109fa7041019b286f9"}, - {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:28c9724a167c84a83fc6238e0781f4702b5fe8c53ede31604525fb1a9d1833f4"}, - {file = "dulwich-0.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c816be529680659b6a19798287b4ec6de49040f58160d40b1b2934fd6c28e93f"}, - {file = "dulwich-0.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b0545f0fa9444a0eb84977d08e302e3f55fd7c34a0466ec28bedc3c839b2fc1f"}, - {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b1682e8e826471ea3c22b8521435e93799e3db8ad05dd3c8f9b1aaacfa78147"}, - {file = "dulwich-0.21.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ad45928a65f39ea0f451f9989b7aaedba9893d48c3189b544a70c6a1043f71"}, - {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1c9e55233f19cd19c484f607cd90ab578ac50ebfef607f77e3b35c2b6049470"}, - {file = "dulwich-0.21.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:18697b58e0fc5972de68b529b08ac9ddda3f39af27bcf3f6999635ed3da7ef68"}, - {file = "dulwich-0.21.6-cp38-cp38-win32.whl", hash = "sha256:22798e9ba59e32b8faff5d9067e2b5a308f6b0fba9b1e1e928571ad278e7b36c"}, - {file = "dulwich-0.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:6c91e1ed20d3d9a6aaaed9e75adae37272b3fcbcc72bab1eb09574806da88563"}, - {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b84450766a3b151c3676fec3e3ed76304e52a84d5d69ade0f34fff2782c1b41"}, - {file = "dulwich-0.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3da632648ee27b64bb5b285a3a94fddf297a596891cca12ac0df43c4f59448f"}, - {file = "dulwich-0.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cef50c0a19f322b7150248b8fa0862ce1652dec657e340c4020573721e85f215"}, - {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ac20dfcfd6057efb8499158d23f2c059f933aefa381e192100e6d8bc25d562"}, - {file = "dulwich-0.21.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d10aa50c0a9a6dd495990c639358e3a3bbff39e17ff302179be6e93b573da7"}, - {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9b52a08d49731375662936d05a12c4a64a6fe0ce257111f62638e475fb5d26d"}, - {file = "dulwich-0.21.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed2f1f638b9adfba862719693b371ffe5d58e94d552ace9a23dea0fb0db6f468"}, - {file = "dulwich-0.21.6-cp39-cp39-win32.whl", hash = "sha256:bf90f2f9328a82778cf85ab696e4a7926918c3f315c75fc432ba31346bfa89b7"}, - {file = "dulwich-0.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e0dee3840c3c72e1d60c8f87a7a715d8eac023b9e1b80199d97790f7a1c60d9c"}, - {file = "dulwich-0.21.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:32d3a35caad6879d04711b358b861142440a543f5f4e02df67b13cbcd57f84a6"}, - {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04df87098053b7767b46fc04b7943d75443f91c73560ca50157cdc22e27a5d3"}, - {file = "dulwich-0.21.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07f145c7b0d82a9f77d157f493a61900e913d1c1f8b1f40d07d919ffb0929a4"}, - {file = "dulwich-0.21.6-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:008ff08629ab16d3638a9f36cfc6f5bd74b4d594657f2dc1583d8d3201794571"}, - {file = "dulwich-0.21.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf469cd5076623c2aad69d01ce9d5392fcb38a5faef91abe1501be733453e37d"}, - {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6592ef2d16ac61a27022647cf64a048f5be6e0a6ab2ebc7322bfbe24fb2b971b"}, - {file = "dulwich-0.21.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99577b2b37f64bc87280079245fb2963494c345d7db355173ecec7ab3d64b949"}, - {file = "dulwich-0.21.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d7cd9fb896c65e4c28cb9332f2be192817805978dd8dc299681c4fe83c631158"}, - {file = "dulwich-0.21.6-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9002094198e57e88fe77412d3aa64dd05978046ae725a16123ba621a7704628"}, - {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b6f8a16f32190aa88c37ef013858b3e01964774bc983900bd0d74ecb6576e6"}, - {file = "dulwich-0.21.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee8aba4dec4d0a52737a8a141f3456229c87dcfd7961f8115786a27b6ebefed"}, - {file = "dulwich-0.21.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a780e2a0ff208c4f218e72eff8d13f9aff485ff9a6f3066c22abe4ec8cec7dcd"}, - {file = "dulwich-0.21.6.tar.gz", hash = "sha256:30fbe87e8b51f3813c131e2841c86d007434d160bd16db586b40d47f31dd05b0"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, + {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, + {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, + {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, + {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, + {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, + {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, + {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, + {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, + {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, + {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, + {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, + {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, + {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, + {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, ] [package.dependencies] @@ -848,18 +829,18 @@ pgp = ["gpg"] [[package]] name = "eip712" -version = "0.2.1" +version = "0.2.2" description = "eip712: Message classes for typed structured data hashing and signing in Ethereum" optional = false python-versions = ">=3.8,<4" files = [ - {file = "eip712-0.2.1-py3-none-any.whl", hash = "sha256:c984c577358d1c7e5d4e52802bf4bd0432e965ba7326448998f95fcc1b6d5269"}, - {file = "eip712-0.2.1.tar.gz", hash = "sha256:3997dace7e581b66a84d106a10baac47a3f6c94095d79c7d0971ca0ede1926ad"}, + {file = "eip712-0.2.2-py3-none-any.whl", hash = "sha256:576476dd1d276e444a633ac22ab25209e18f8f41e5016e576a132d190043a4ba"}, + {file = "eip712-0.2.2.tar.gz", hash = "sha256:6d2e07a83c66fb1cbe2448bb4dfea1c91913c4822b7d9b89231e5b61473ae426"}, ] [package.dependencies] dataclassy = ">=0.8.2,<1" -eth-abi = ">=4.0.0,<5" +eth-abi = ">=4.1.0,<5" eth-account = ">=0.8.0,<0.9" eth-hash = {version = "*", extras = ["pycryptodome"]} eth-typing = ">=3.3.0,<4" @@ -867,9 +848,9 @@ eth-utils = ">=2.1.0,<3" hexbytes = ">=0.3.0,<1" [package.extras] -dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.1.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.1.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] +dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.7.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.5.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] doc = ["Sphinx (>=5.3.0,<6)", "myst-parser (>=0.18.1,<0.19)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.1.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.1.1,<2)", "types-setuptools"] +lint = ["black (>=23.7.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.5.1,<2)", "types-setuptools"] release = ["setuptools", "twine", "wheel"] test = ["hypothesis (>=6.70.0,<7)", "pytest (>=6.0,<8)", "pytest-cov", "pytest-xdist"] @@ -925,42 +906,40 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-x [[package]] name = "eth-bloom" -version = "2.0.0" -description = "Python implementation of the Ethereum Trie structure" +version = "3.0.0" +description = "A python implementation of the bloom filter used by Ethereum" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-bloom-2.0.0.tar.gz", hash = "sha256:73576828dff7566b9216403e0898966912f370bae5734241dd3f50ce5664a825"}, - {file = "eth_bloom-2.0.0-py3-none-any.whl", hash = "sha256:cc86ab9670577996f7fcb8445b7a164ecd211ac91d9c4c2b5a47678623419927"}, + {file = "eth-bloom-3.0.0.tar.gz", hash = "sha256:94bab384b01f2eb1012abbd6bb504e4c743878414d8695ee5a5d25f4247b3886"}, + {file = "eth_bloom-3.0.0-py3-none-any.whl", hash = "sha256:bb884ece93d292dfbbe4696744db874a88ac5bfc45f6f1b0ee147d801604a46c"}, ] [package.dependencies] eth-hash = {version = ">=0.4.0", extras = ["pycryptodome"]} [package.extras] -deploy = ["bumpversion", "wheel"] -dev = ["black (>=22.1.0)", "build", "bumpversion", "flake8 (>=3.8.3)", "hypothesis (>=3.31.2)", "isort (>=4.2.15)", "mypy (==0.910)", "pytest (>=6.2.5)", "tox (>=2.6.0)", "twine", "wheel"] -lint = ["black (>=22.1.0)", "flake8 (>=3.8.3)", "isort (>=4.2.15)", "mypy (==0.910)"] -test = ["hypothesis (>=3.31.2)", "pytest (>=6.2.5)", "tox (>=2.6.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (>=3.31.2)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["hypothesis (>=3.31.2)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-hash" -version = "0.5.2" +version = "0.6.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-hash-0.5.2.tar.gz", hash = "sha256:1b5f10eca7765cc385e1430eefc5ced6e2e463bb18d1365510e2e539c1a6fe4e"}, - {file = "eth_hash-0.5.2-py3-none-any.whl", hash = "sha256:251f62f6579a1e247561679d78df37548bd5f59908da0b159982bf8293ad32f0"}, + {file = "eth-hash-0.6.0.tar.gz", hash = "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478"}, + {file = "eth_hash-0.6.0-py3-none-any.whl", hash = "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3"}, ] [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] @@ -1050,33 +1029,33 @@ hypothesis = ["hypothesis (>=6.58.0,<7.0.0)"] [[package]] name = "eth-typing" -version = "3.5.0" +version = "3.5.2" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = ">=3.7.2, <4" files = [ - {file = "eth-typing-3.5.0.tar.gz", hash = "sha256:a92f6896896752143a4704c57441eedf7b1f65d5df4b1c20cb802bb4aa602d7e"}, - {file = "eth_typing-3.5.0-py3-none-any.whl", hash = "sha256:a773dbb7d78fcd1539c30264193ca26ec965f3abca2711748e307f117b0a10f5"}, + {file = "eth-typing-3.5.2.tar.gz", hash = "sha256:22bf051ddfaa35ff827c30090de167e5c5b8cc6d343f7f35c9b1c7553f6ab64d"}, + {file = "eth_typing-3.5.2-py3-none-any.whl", hash = "sha256:1842e628fb1ffa929b94f89a9d33caafbeb9978dc96abb6036a12bc91f1c624b"}, ] [package.dependencies] typing-extensions = ">=4.0.1" [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "types-setuptools"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "2.2.2" +version = "2.3.1" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" optional = false python-versions = ">=3.7,<4" files = [ - {file = "eth-utils-2.2.2.tar.gz", hash = "sha256:5ca6265177ce544d9d43cdf2272ae2227e5d6d9529c270bbb707d17339087101"}, - {file = "eth_utils-2.2.2-py3-none-any.whl", hash = "sha256:2580a8065273f62ca1ec4c175228c52e626a5f1007e965d2117e5eca1a93cae8"}, + {file = "eth-utils-2.3.1.tar.gz", hash = "sha256:56a969b0536d4969dcb27e580521de35abf2dbed8b1bf072b5c80770c4324e27"}, + {file = "eth_utils-2.3.1-py3-none-any.whl", hash = "sha256:614eedc5ffcaf4e6708ca39e23b12bd69526a312068c1170c773bd1307d13972"}, ] [package.dependencies] @@ -1093,13 +1072,13 @@ test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-x [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -1121,13 +1100,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "executing" -version = "2.0.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, - {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] @@ -1150,19 +1129,19 @@ pyrepl = ">=0.8.2" [[package]] name = "filelock" -version = "3.12.4" +version = "3.13.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" @@ -1220,22 +1199,22 @@ lxml = ["lxml"] [[package]] name = "hypothesis" -version = "6.88.0" +version = "6.92.9" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.88.0-py3-none-any.whl", hash = "sha256:b52b5b5a5065340875fb8a1a45e45391c277d9c5765374560edc1c5e5c3e2d48"}, - {file = "hypothesis-6.88.0.tar.gz", hash = "sha256:c9096ccd5a78bbf75221a2b4a6149e00e254acb17637c94abab98c529b2f61e5"}, + {file = "hypothesis-6.92.9-py3-none-any.whl", hash = "sha256:8c1ab9f3c883fe63a712bb6c8c1b5be4185cad52775cd7703c040fc0d0111572"}, + {file = "hypothesis-6.92.9.tar.gz", hash = "sha256:629f31788243559d35d3101ef8e94caf736cf8efaad3f0dd66ec7dbb31b8ef19"}, ] [package.dependencies] -attrs = ">=19.2.0" +attrs = ">=22.2.0" exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.4)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] @@ -1248,17 +1227,17 @@ pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.4)"] [[package]] name = "identify" -version = "2.5.30" +version = "2.5.33" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, - {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -1266,31 +1245,31 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.0.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -1318,42 +1297,39 @@ files = [ [[package]] name = "ipython" -version = "8.16.1" +version = "8.20.0" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "ipython-8.16.1-py3-none-any.whl", hash = "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e"}, - {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"}, + {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"}, + {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "isort" @@ -1426,13 +1402,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.20.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"}, + {file = "jsonschema-4.20.0.tar.gz", hash = "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa"}, ] [package.dependencies] @@ -1447,17 +1423,17 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.7.1" +version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, - {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, ] [package.dependencies] -referencing = ">=0.28.0" +referencing = ">=0.31.0" [[package]] name = "keyring" @@ -1484,13 +1460,13 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec [[package]] name = "lark" -version = "1.1.7" +version = "1.1.9" description = "a modern parsing library" optional = false python-versions = ">=3.6" files = [ - {file = "lark-1.1.7-py3-none-any.whl", hash = "sha256:9e5dc5bbf93fa1840083707285262514a0ef8a6613874af7ea1cec60468d6e92"}, - {file = "lark-1.1.7.tar.gz", hash = "sha256:be7437bf1f37ab08b355f29ff2571d77d777113d0a8c4352b0c513dced6c5a1e"}, + {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"}, + {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"}, ] [package.extras] @@ -1512,93 +1488,92 @@ files = [ [[package]] name = "lru-dict" -version = "1.2.0" +version = "1.3.0" description = "An Dict like LRU container." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, - {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, - {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, - {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, - {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, - {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, - {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, - {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, - {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, - {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, - {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, - {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, - {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, - {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, - {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, + {file = "lru-dict-1.3.0.tar.gz", hash = "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4073333894db9840f066226d50e6f914a2240711c87d60885d8c940b69a6673f"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0ad6361e4dd63b47b2fc8eab344198f37387e1da3dcfacfee19bafac3ec9f1eb"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c637ab54b8cd9802fe19b260261e38820d748adf7606e34045d3c799b6dde813"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fce5f95489ca1fc158cc9fe0f4866db9cec82c2be0470926a9080570392beaf"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2bf2e24cf5f19c3ff69bf639306e83dced273e6fa775b04e190d7f5cd16f794"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90059f7701bef3c4da073d6e0434a9c7dc551d5adce30e6b99ef86b186f4b4a"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ecb7ae557239c64077e9b26a142eb88e63cddb104111a5122de7bebbbd00098"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6af36166d22dba851e06a13e35bbf33845d3dd88872e6aebbc8e3e7db70f4682"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee38d420c77eed548df47b7d74b5169a98e71c9e975596e31ab808e76d11f09"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0e1845024c31e6ff246c9eb5e6f6f1a8bb564c06f8a7d6d031220044c081090b"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ca5474b1649555d014be1104e5558a92497509021a5ba5ea6e9b492303eb66b"}, + {file = "lru_dict-1.3.0-cp310-cp310-win32.whl", hash = "sha256:ebb03a9bd50c2ed86d4f72a54e0aae156d35a14075485b2127c4b01a3f4a63fa"}, + {file = "lru_dict-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:04cda617f4e4c27009005d0a8185ef02829b14b776d2791f5c994cc9d668bc24"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a"}, + {file = "lru_dict-1.3.0-cp311-cp311-win32.whl", hash = "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4"}, + {file = "lru_dict-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df"}, + {file = "lru_dict-1.3.0-cp312-cp312-win32.whl", hash = "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc"}, + {file = "lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c265f16c936a8ff3bb4b8a4bda0be94c15ec28b63e99fdb1439c1ffe4cd437db"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:784ca9d3b0730b3ec199c0a58f66264c63dd5d438119c739c349a6a9be8e5f6e"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e13b2f58f647178470adaa14603bb64cc02eeed32601772ccea30e198252883c"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ffbce5c2e80f57937679553c8f27e61ec327c962bf7ea0b15f1d74277fd5363"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7969cb034b3ccc707aff877c73c225c32d7e2a7981baa8f92f5dd4d468fe8c33"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca9ab676609cce85dd65d91c275e47da676d13d77faa72de286fbea30fbaa596"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27c078b5d75989952acbf9b77e14c3dadc468a4aafe85174d548afbc5efc38b"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6123aefe97762ad74215d05320a7f389f196f0594c8813534284d4eafeca1a96"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd869cadba9a63e1e7fe2dced4a5747d735135b86016b0a63e8c9e324ab629ac"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40a8daddc29c7edb09dfe44292cf111f1e93a8344349778721d430d336b50505"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a03170e4152836987a88dcebde61aaeb73ab7099a00bb86509d45b3fe424230"}, + {file = "lru_dict-1.3.0-cp38-cp38-win32.whl", hash = "sha256:3b4f121afe10f5a82b8e317626eb1e1c325b3f104af56c9756064cd833b1950b"}, + {file = "lru_dict-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:1470f5828c7410e16c24b5150eb649647986e78924816e6fb0264049dea14a2b"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c9f746a9917e784fffcedeac4c8c47a3dbd90cbe13b69e9140182ad97ce4b7"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2789296819525a1f3204072dfcf3df6db8bcf69a8fc740ffd3de43a684ea7002"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:170b66d29945391460351588a7bd8210a95407ae82efe0b855e945398a1d24ea"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774ca88501a9effe8797c3db5a6685cf20978c9cb0fe836b6813cfe1ca60d8c9"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df2e119c6ae412d2fd641a55f8a1e2e51f45a3de3449c18b1b86c319ab79e0c4"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28aa1ea42a7e48174bf513dc2416fea7511a547961e678dc6f5670ca987c18cb"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9537e1cee6fa582cb68f2fb9ce82d51faf2ccc0a638b275d033fdcb1478eb80b"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:64545fca797fe2c68c5168efb5f976c6e1459e058cab02445207a079180a3557"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a193a14c66cfc0c259d05dddc5e566a4b09e8f1765e941503d065008feebea9d"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb1de0ce4137b060abaafed8474cc0ebd12cedd88aaa7f7b3ebb1ddfba86ae0"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8551ccab1349d4bebedab333dfc8693c74ff728f4b565fe15a6bf7d296bd7ea9"}, + {file = "lru_dict-1.3.0-cp39-cp39-win32.whl", hash = "sha256:6cb0be5e79c3f34d69b90d8559f0221e374b974b809a22377122c4b1a610ff67"}, + {file = "lru_dict-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9f725f2a0bdf1c18735372d5807af4ea3b77888208590394d4660e3d07971f21"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f8f7824db5a64581180ab9d09842e6dd9fcdc46aac9cb592a0807cd37ea55680"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acd04b7e7b0c0c192d738df9c317093335e7282c64c9d1bb6b7ebb54674b4e24"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5c20f236f27551e3f0adbf1a987673fb1e9c38d6d284502cd38f5a3845ef681"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca3703ff03b03a1848c563bc2663d0ad813c1cd42c4d9cf75b623716d4415d9a"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9fb71ba262c6058a0017ce83d343370d0a0dbe2ae62c2eef38241ec13219330"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f5b88a7c39e307739a3701194993455968fcffe437d1facab93546b1b8a334c1"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2682bfca24656fb7a643621520d57b7fe684ed5fa7be008704c1235d38e16a32"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96fc87ddf569181827458ec5ad8fa446c4690cffacda66667de780f9fcefd44d"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec98e2c7da7631f0811730303abc4bdfe70d013f7a11e174a2ccd5612a7c59"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6bba2863060caeaedd8386b0c8ee9a7ce4d57a7cb80ceeddf440b4eff2d013ba"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c497fb60279f1e1d7dfbe150b1b069eaa43f7e172dab03f206282f4994676c5"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9509d817a47597988615c1a322580c10100acad10c98dfcf3abb41e0e5877f"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0213ab4e3d9a8d386c18e485ad7b14b615cb6f05df6ef44fb2a0746c6ea9278b"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50fbd69cd3287196796ab4d50e4cc741eb5b5a01f89d8e930df08da3010c385"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5247d1f011f92666010942434020ddc5a60951fefd5d12a594f0e5d9f43e3b3b"}, ] [package.extras] @@ -1623,6 +1598,30 @@ pathspec = ">=0.9.0" [package.extras] dev = ["black (>=22.6.0)", "bump2version (>=1.0.0)", "coverage (>=6.0.0)", "flake8 (==5.0.4)", "mypy (>=0.900)", "mypy-extensions (==0.4.3)", "pylint (==2.14.5)", "pytest (>=6.0.0)", "pytest-cov (>=3.0.0)"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "matplotlib-inline" version = "0.1.6" @@ -1648,15 +1647,26 @@ files = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "more-itertools" -version = "10.1.0" +version = "10.2.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, - {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, + {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, + {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, ] [[package]] @@ -1790,13 +1800,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] @@ -1821,29 +1831,18 @@ testing = ["funcsigs", "pytest"] [[package]] name = "pexpect" -version = "4.8.0" +version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] - [[package]] name = "pkginfo" version = "1.9.6" @@ -1956,13 +1955,13 @@ poetry-core = ">=1.6.0,<2.0.0" [[package]] name = "pre-commit" -version = "3.5.0" +version = "3.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, ] [package.dependencies] @@ -1974,13 +1973,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -2046,13 +2045,13 @@ test = ["pytest (==6.2.5)", "pytest-xdist (==1.26.0)"] [[package]] name = "py-evm" -version = "0.7.0a4" +version = "0.8.0b1" description = "Python implementation of the Ethereum Virtual Machine" optional = false python-versions = "*" files = [ - {file = "py-evm-0.7.0a4.tar.gz", hash = "sha256:d40b6ac950485111dc7ad7bd29e3f61e00d5f81dc919e8c2b3afca30f228dc05"}, - {file = "py_evm-0.7.0a4-py3-none-any.whl", hash = "sha256:1bf7b293faa70c03727358ae3e5cb0abf7282391461d9b52b82decd6ed18c2f7"}, + {file = "py-evm-0.8.0b1.tar.gz", hash = "sha256:a082eeef14d5189b7f98c76c2b3d75f2bdbe205447e57add27c1c392f5d55544"}, + {file = "py_evm-0.8.0b1-py3-none-any.whl", hash = "sha256:ae22ab813406c248f085caac6d689f3ce8f60ae60e861df6db1618e24c9e503e"}, ] [package.dependencies] @@ -2064,18 +2063,17 @@ eth-utils = ">=2.0.0,<3.0.0" lru-dict = ">=1.1.6" mypy-extensions = ">=1.0.0" py-ecc = ">=1.4.7,<7.0.0" -pyethash = ">=0.1.27,<1.0.0" rlp = ">=3,<4" trie = ">=2.0.0,<3" [package.extras] benchmark = ["termcolor (>=1.1.0,<2.0.0)", "web3 (>=4.1.0,<5.0.0)"] -dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==1.4.0)", "mypy-extensions (>=1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pyethash (>=0.1.27,<1.0.0)", "pysha3 (>=1.0.0,<2.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] -docs = ["Sphinx (>=1.5.5,<2)", "jinja2 (>=3.0.0,<3.1.0)", "py-evm (>=0.2.0-a.14)", "pysha3 (>=1.0.0,<2.0.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)"] -eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "pyethash (>=0.1.27,<1.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] -eth-extra = ["blake2b-py (>=0.1.4,<0.2)", "coincurve (>=13.0.0,<14.0.0)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "plyvel (>=1.2.0,<2)"] +dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==1.4.0)", "mypy-extensions (>=1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=2.0.0,<3)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (>=3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] +docs = ["Sphinx (>=1.5.5,<2)", "jinja2 (>=3.0.0,<3.1.0)", "py-evm (>=0.2.0-a.14)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)"] +eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] +eth-extra = ["blake2b-py (>=0.2.0,<0.3.0)", "coincurve (>=18.0.0)"] lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==1.4.0)", "pydocstyle (>=6.0.0)", "types-setuptools"] -test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=1.4.2,<2)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (==2.3.0)"] +test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=2.0.0,<3)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (>=3.0)"] [[package]] name = "pycodestyle" @@ -2101,53 +2099,43 @@ files = [ [[package]] name = "pycryptodome" -version = "3.19.0" +version = "3.20.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, - {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, -] - -[[package]] -name = "pyethash" -version = "0.1.27" -description = "Python wrappers for ethash, the ethereum proof of workhashing function" -optional = false -python-versions = "*" -files = [ - {file = "pyethash-0.1.27.tar.gz", hash = "sha256:ff66319ce26b9d77df1f610942634dac9742e216f2c27b051c0a2c2dec9c2818"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] [[package]] @@ -2163,17 +2151,18 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyproject-hooks" @@ -2211,13 +2200,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -2262,13 +2251,13 @@ pytest = "*" [[package]] name = "pytest-xdist" -version = "3.3.1" +version = "3.5.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, - {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, + {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, + {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, ] [package.dependencies] @@ -2352,119 +2341,101 @@ files = [ [[package]] name = "rapidfuzz" -version = "2.15.2" +version = "3.6.1" description = "rapid fuzzy string matching" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b2e64e08588965b2490ee6b581d3901dd207ec3f6919b1c8da495183acfde953"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0af367ecb515ae695d7da21b0bd05784f388621e9d6a2e21dc96e6ba5d18d95f"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:892d0d75f0b820d949b0bf9502f746cfcbaab98d8a47653fa8369607fde250f1"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcf1d564ec948a4bf0750252579871be1790de66200f4cf8d624446017d74ee9"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab2f86733fe34cd825b6cbc688d41b7eb19ae0ce1ea7dc57eac13862d4b9ecb5"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bdc497a8930428fa35158c58a744ddaa930621b80adfb61884456d8f184288a"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97f6c4948ca07ad1a30e70da56ec672422ef6bf18d10b6a881e7a64ba73a126d"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f3e2cc54edffd62ae38a03802b79c0f0cec6c2f89819607350fb5c4c00442d7"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a252ccb39d628d0f68bab80ba18a02e0d1853a0ec71991e665a6bf81a28c79a"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff82edd7ff9796e2ca349aa583fcb6b9ae96db0b6c5a76dcf0c1f67b1cb86964"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0860877f455833e5ed7113e859a9b2bf9670b22fdc7a48b81384a04c4a8e8a48"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:1a78c75ad082fdd58fdcf04551b7737c96aa9e870f1b008b881fc179e7dc6208"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a9df54f67a22a2447b8b6648880de9ede5e2a2e568644e1de770df9bef5c2fb4"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-win32.whl", hash = "sha256:055e85bb1237142da4ed024f9986c3720d484036f8dd550b090582f288b71bb9"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:8f220df380c127ef8a9129d8878dabf99ed0f543597cf81dfdd30eca03843666"}, - {file = "rapidfuzz-2.15.2-cp310-cp310-win_arm64.whl", hash = "sha256:49972e202251ba60de41a7add8e86a055478020eabf3339300f46a8fdc35d048"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29352510bcc2b7c3c7f3c1ab6f4c2115dc640cd79a9dc8e01adbae19fb96d359"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae3f741b9b3e95908158e6e56a5f11c1abc51754801dccd495e5cba734c541e"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a716bbded611cc82f7b27dcd7335b7bae49706c97a8738283464ff1536e7407"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ff36fb50f02259402d7cbdc96f75671b2cb14550db5ad6534a09a7f4940d796"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d60a2368e2564155d7209143a6b1dafa1eb457f31cf44698f917cba608d2341f"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c02fd6d75de19633f622daf6584cb6ed3148eac3a2b6b08fd3539c166de2921f"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5c875da0e0c9709dbdc6e33a7f061192e98943817e6d0e1f5d1d8b07050e349"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb74dcfadf0c5f520074455fe51fa0f62876e5473f5f60521d153afef888ef70"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b31f65137e8e45c4fb2dda394bb31598cff8290fb0ce5e66c8cf47d1bc554cb"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:689008633f88cf8802dbd281ac745775aeeee67525d532fcbabda0c8bc5b2e32"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:02fd52352346c965fdc9de9d26f55d61941cc27c876a589eeb3f4efdb7dffdb1"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:454ab8b5c8fc526243133dab013f0a3355efcc1200829cfba7ef56280c7763fc"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fd40f263d1ad1cdd4b657e867654674315eea9abf3fce64269610b7bc81265ee"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-win32.whl", hash = "sha256:66db4817c54a6ca91234959c4f6d0cb1fd943ddfb379ee7f9e6dce99b522554e"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f8eaf74105ffea1d15198b109ff0ca7b6dccafc61e05fa5f98a53d925707c57"}, - {file = "rapidfuzz-2.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed0ec102b5e405d7562e4df05729a89467ae5c8a364c52fcf8c129398e82e6c5"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c0c8475f029a50bf65571b59d332fccd3eb33c5e49283868490a973e9ca7c33c"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ee9ee24eb431d5f73d0b255dc8e66272967a58cd6670cca984a81bbfc7dde904"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1ecd818c108cefea2c02a9a716e223f811e612a050c8625555336b65d1cabef"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3eda119ebcf501dc35054abd9a187b5249b3d93b3965485371efb48e735b72c"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7ba83d0846991f67c2ec12ff8530b5e0f929e32a57352080b5f95aade0a62e"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c279864902a9538b17547e0d9399f05f36ebb9f3356bc5bc4cec2ba137fa5a17"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c94e247011fa7eea14d210123ebda2ecdf98ccc114254353edb4501ee8a19d7"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675c9052b3a04a4b33c92f0b8952ef2439163853422cc583286351ee82fc4d26"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d64820ae7a795082208a2d762c6a291aca116b86e35c2831e468ae3d4bb5cd"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c0f12cc4a8216edfaa0511aae34d8b2f824a05cfe5a26a08de9cf180ae584e88"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e27da009ef39dc64297bcdf09c8d4c79ac90d0015fcf0a01af2a802cd7e1803"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:ea541d56fbb7de717a013790c2bce655252da220f23db0c6ce24f628cbe228e6"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f52338e4e69aff4260c84275c7a704d198315b9b84303e67e584971409347d0"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-win32.whl", hash = "sha256:d5550e0078b2618c4ea7ea761053337eb7c5f5cc515f4941d8108ce9b0c7ee8c"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:19f72cfe2553c83c5e383851aba2891dafbb6446b6ae1ec0637333558ddd564e"}, - {file = "rapidfuzz-2.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:423ef2ca785da77cd081d5bbc57035dc9b91500008a1b8e8e811a0ba3871a5ee"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0a02f1b08879a74aa7b4e562823f67a2e913fe3bd18c5346d9270d16fc588500"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a100ca26804b9ac2b2c0f70c632102bc0005d2cafe6d748f5d01dbe569c378bf"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e9fb88659cff92eba1b441efe426a4c349372137ee713b3a3933cc6ead73234"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58073d3ebed8c0f51e163654dcb5e34f1e8b67f7b23361441861c6021243184b"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f55ad06ff79c2ffa3d1f5b38ce8f3082fa4db57c04be7de85243bd0625ca4ef"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceecb57ec9e5c0d5bd9bd2881731c59cdc9a2c51711fd0b29b5bf14bdcab465f"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6c32c855e16ef3890037569f6f1299857172c674cd8946244e5fb7d5cacb771a"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e46f82fda6f969da8be5a8f33a057b2a9c6e7b80ab8679344a72e6fb708a48fc"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6edc9b138797c60c1276171d8c97f53b17e304ade37c022ff97b1e995f79ba79"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b32e4fd756a32f92b6f8b707a682ab4054b90c835021c01d81baba22f6277172"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5fb89d3a8d389eca258aba913adc81a8b8231b48896abbcb2f05768455584c4e"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-win32.whl", hash = "sha256:03ceea6cc9e4442379aa8581fbe61bad6e12d7938b16fbdc8442c8d915ad1154"}, - {file = "rapidfuzz-2.15.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cb9f24fafb5ed77fc2ce23b1d8351efcfdb4c05b5f3b96bf004e89344a3d30ed"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aab133bea22acbd3fa3740989a2f21d0e275efede2bf406a25a84392086c32f9"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e110224e0de4fe4876224104a79550d18df15459fe94adf24b4b644e31d69cc"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:780b006bd007e4a071a9c022733f56b0df1f8c269bb7e9dbe079a79e8d9d3b8d"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:898bee3fd785ee695d4cb0d3c689407809cafca472851904aa78143ca6634903"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34623f51ed5dcbb2ddb97b2fefda34e7b53a047c71aac5ec6b72e42d5263f8b2"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02b3612c9318006290e6e6d82f1f98b83aa4cf062075c5ea03fac71ba4d31499"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dd0aab9ffab0010ae28b60f64c98c09c93086b3dc0cb3da863e53a3ca14a2bd"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e772677a84a166531f975301cb91db234a56eb5b6785e79ff5cb335251580efc"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7a670aed23d9a8d27a0031fa059e8f50f3f7287bd5a075a448251029794de9"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:830f799e5ec534633dee3b26c6d5398461dd3ced22118ab590f7fd0f91263058"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e427a9c9c1a8adac7b0293ddfe8f5885edf4f425cfd8a3b7ceae20434ec0663c"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3a3df80a264a999a120e637f98a1460d4f2c815323dd605e2022eef97db55448"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1496540d2ce8b1b9f340e652b9306674fa657d8d3a0b9629421cf31ace219092"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-win32.whl", hash = "sha256:aabd9da406fec009c08d2cd1bfa444ee568edf8e7c9a9d5e609885fc81c243a3"}, - {file = "rapidfuzz-2.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:d21c66b15fbe253d48399a9d9db361ab2b3462a59b78c9279d9d7d347f5ded91"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ef4dea11b87234e8b08ee47df9d869ae071bdacb5e55df82673ab9fa622f1e0"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ee3d9bc953f232bffcbd973137505f6cf5be5ed9c2cdc5e4a5db4be33bf5a734"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb94f6adbbbdacac9f687eb151ae9220ee9f141bb259fe07e82a2087114c17e"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9c3e07d13661871aebc325b9b3acbd42355a1df1e21ad0435fc81980fd20607"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01bae563a010900abba857e485c3747a78d61c88431cc3d9bea894c7c3e521f"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09187df670e344468597b2c6f5ddc7651be75c4b594baa62c9261a144e5c058"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcbfe5497c93a1b8717ea38b41b47f7e9d155fbc36a6bbfa84b8c901875465af"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f997a93b85c5798fe139a46c68c85de06ff75b4fd52d52463e46573bff39774"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:199676b8a19746017a0fbad0eb11380cbda4f635b6d2ee477544743b7f99d947"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:499a170088049258d5118bff8cf88f88ef6054544edbea0f2920eba8669e5eb9"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a69ebe7b493557c425ca1d64bf0b5599f0405772b5179070adc2f62f7867836f"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00bd97cd31aad049400b70e0872b54457c4769b296176d5b064f6a5d6391909f"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cadabe1287314bc5053f57c6043df04e33cf5fba33514ca0f4c7b0b8476063a0"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-win32.whl", hash = "sha256:301709491a7960473c34501602cd85a7653df7e0d4189c0ded1e0fd86a83b6ca"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c968a2330b6f2de93e6d54ef7ebd5e5724ee730cd6f225e977cebc7af1df366"}, - {file = "rapidfuzz-2.15.2-cp39-cp39-win_arm64.whl", hash = "sha256:c6776c27385f3fe5810f3c389f01957d5fa6c3c7f7a76fd9815f2933674f787f"}, - {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0b4c632b684478fd8780970685a0c575a5bee65692727ff9898acf75d61cb3ff"}, - {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b1cfca399461e1f534fbeb3c87f39f2c37ed71f8d1dfb02b78a5b3f81bf0ef"}, - {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba35ec7256a86270a5e2d193ff0089cf84787a1aa94a48f5f6105f86feb8ca38"}, - {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdfc137bbe2e942321f725004395444d2594077932ad55f927d6b6e884c09142"}, - {file = "rapidfuzz-2.15.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:153366a00ea22e79f051298fb9606bf9472bca5ce1b82319070fcbea2f7b97d7"}, - {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6bf1c60432755ed8ab5870a932b7c9382435a240d727d3b5e68f9ff9f83a3556"}, - {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a358eb275eadad0ac44f0fdb2255d6b373908c742f94e06b2190dbfaaaaa49b8"}, - {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34136ab5bbd1b9643f9072102a88471995100b5d734cfaa946d3b63e332e653"}, - {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:796e53c5f78c159aff8e5003bca41bfe007c6a63ee7e7a289765a7db30429197"}, - {file = "rapidfuzz-2.15.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2ce4a91be05c28b57d5019b09cf0970305760623e34da95f2cddd9067e7fe91d"}, - {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:237d5b4cbfacdef0a84f2ead0b4819c586bb74d05f4a380bd2f8489464b7b7fa"}, - {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773dff970af0474d7d551a953a0075840ced30315d4885e038a289857ed33365"}, - {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c536fbbebb496a76cac3a45f139bf023807b1fb6e2262e77f875fc9b6802ec4e"}, - {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e85579a698c9436c2dac1583d4b07cca635faeb9a7adeab03d42938ec0fe9f58"}, - {file = "rapidfuzz-2.15.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:77c540546c0ea7cb229cd9823f9cd174c93988657727880bfdd6db7f353f93d6"}, - {file = "rapidfuzz-2.15.2.tar.gz", hash = "sha256:bfc1d38a7adcbe8912f980a5f46f27a801dd8655582ff0d4a2c0431c02b7ce33"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f"}, + {file = "rapidfuzz-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win32.whl", hash = "sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe"}, + {file = "rapidfuzz-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win32.whl", hash = "sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1"}, + {file = "rapidfuzz-3.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-win32.whl", hash = "sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105"}, + {file = "rapidfuzz-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win32.whl", hash = "sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198"}, + {file = "rapidfuzz-3.6.1-cp39-cp39-win_arm64.whl", hash = "sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c"}, + {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec"}, + {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b"}, + {file = "rapidfuzz-3.6.1.tar.gz", hash = "sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7"}, ] [package.extras] @@ -2472,13 +2443,13 @@ full = ["numpy"] [[package]] name = "referencing" -version = "0.30.2" +version = "0.32.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, - {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, + {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, + {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, ] [package.dependencies] @@ -2487,99 +2458,104 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2023.10.3" +version = "2023.12.25" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.7" files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, ] [[package]] @@ -2619,21 +2595,21 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" -version = "12.6.0" +version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.6.3,<4.0.0" +python-versions = ">=3.7.0" files = [ - {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, - {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, ] [package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" [package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rlp" @@ -2658,110 +2634,110 @@ test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] [[package]] name = "rpds-py" -version = "0.10.6" +version = "0.16.2" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, - {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, - {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, - {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, - {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, - {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, - {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, - {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, - {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, - {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, - {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, - {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, - {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, + {file = "rpds_py-0.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:509b617ac787cd1149600e731db9274ebbef094503ca25158e6f23edaba1ca8f"}, + {file = "rpds_py-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:413b9c17388bbd0d87a329d8e30c1a4c6e44e2bb25457f43725a8e6fe4161e9e"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2946b120718eba9af2b4dd103affc1164a87b9e9ebff8c3e4c05d7b7a7e274e2"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35ae5ece284cf36464eb160880018cf6088a9ac5ddc72292a6092b6ef3f4da53"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc6a7620ba7639a3db6213da61312cb4aa9ac0ca6e00dc1cbbdc21c2aa6eb57"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cb6fe8ecdfffa0e711a75c931fb39f4ba382b4b3ccedeca43f18693864fe850"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dace7b26a13353e24613417ce2239491b40a6ad44e5776a18eaff7733488b44"}, + {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bdbc5fcb04a7309074de6b67fa9bc4b418ab3fc435fec1f2779a0eced688d04"}, + {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f42e25c016927e2a6b1ce748112c3ab134261fc2ddc867e92d02006103e1b1b7"}, + {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eab36eae3f3e8e24b05748ec9acc66286662f5d25c52ad70cadab544e034536b"}, + {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0474df4ade9a3b4af96c3d36eb81856cb9462e4c6657d4caecfd840d2a13f3c9"}, + {file = "rpds_py-0.16.2-cp310-none-win32.whl", hash = "sha256:84c5a4d1f9dd7e2d2c44097fb09fffe728629bad31eb56caf97719e55575aa82"}, + {file = "rpds_py-0.16.2-cp310-none-win_amd64.whl", hash = "sha256:2bd82db36cd70b3628c0c57d81d2438e8dd4b7b32a6a9f25f24ab0e657cb6c4e"}, + {file = "rpds_py-0.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:adc0c3d6fc6ae35fee3e4917628983f6ce630d513cbaad575b4517d47e81b4bb"}, + {file = "rpds_py-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec23fcad480e77ede06cf4127a25fc440f7489922e17fc058f426b5256ee0edb"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07aab64e2808c3ebac2a44f67e9dc0543812b715126dfd6fe4264df527556cb6"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4ebb8b20bd09c5ce7884c8f0388801100f5e75e7f733b1b6613c713371feefc"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3d7e2ea25d3517c6d7e5a1cc3702cffa6bd18d9ef8d08d9af6717fc1c700eed"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f28ac0e8e7242d140f99402a903a2c596ab71550272ae9247ad78f9a932b5698"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19f00f57fdd38db4bb5ad09f9ead1b535332dbf624200e9029a45f1f35527ebb"}, + {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3da5a4c56953bdbf6d04447c3410309616c54433146ccdb4a277b9cb499bc10e"}, + {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec2e1cf025b2c0f48ec17ff3e642661da7ee332d326f2e6619366ce8e221f018"}, + {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e0441fb4fdd39a230477b2ca9be90868af64425bfe7b122b57e61e45737a653b"}, + {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9f0350ef2fba5f34eb0c9000ea328e51b9572b403d2f7f3b19f24085f6f598e8"}, + {file = "rpds_py-0.16.2-cp311-none-win32.whl", hash = "sha256:5a80e2f83391ad0808b4646732af2a7b67550b98f0cae056cb3b40622a83dbb3"}, + {file = "rpds_py-0.16.2-cp311-none-win_amd64.whl", hash = "sha256:e04e56b4ca7a770593633556e8e9e46579d66ec2ada846b401252a2bdcf70a6d"}, + {file = "rpds_py-0.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e6caa3809e50690bd92fa490f5c38caa86082c8c3315aa438bce43786d5e90d"}, + {file = "rpds_py-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e53b9b25cac9065328901713a7e9e3b12e4f57ef4280b370fbbf6fef2052eef"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af27423662f32d7501a00c5e7342f7dbd1e4a718aea7a239781357d15d437133"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d4dd5fb16eb3825742bad8339d454054261ab59fed2fbac84e1d84d5aae7ba"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e061de3b745fe611e23cd7318aec2c8b0e4153939c25c9202a5811ca911fd733"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b811d182ad17ea294f2ec63c0621e7be92a1141e1012383461872cead87468f"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5552f328eaef1a75ff129d4d0c437bf44e43f9436d3996e8eab623ea0f5fcf73"}, + {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dcbe1f8dd179e4d69b70b1f1d9bb6fd1e7e1bdc9c9aad345cdeb332e29d40748"}, + {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8aad80645a011abae487d356e0ceb359f4938dfb6f7bcc410027ed7ae4f7bb8b"}, + {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6f5549d6ed1da9bfe3631ca9483ae906f21410be2445b73443fa9f017601c6f"}, + {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d452817e0d9c749c431a1121d56a777bd7099b720b3d1c820f1725cb40928f58"}, + {file = "rpds_py-0.16.2-cp312-none-win32.whl", hash = "sha256:888a97002e986eca10d8546e3c8b97da1d47ad8b69726dcfeb3e56348ebb28a3"}, + {file = "rpds_py-0.16.2-cp312-none-win_amd64.whl", hash = "sha256:d8dda2a806dfa4a9b795950c4f5cc56d6d6159f7d68080aedaff3bdc9b5032f5"}, + {file = "rpds_py-0.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:071980663c273bf3d388fe5c794c547e6f35ba3335477072c713a3176bf14a60"}, + {file = "rpds_py-0.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:726ac36e8a3bb8daef2fd482534cabc5e17334052447008405daca7ca04a3108"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9e557db6a177470316c82f023e5d571811c9a4422b5ea084c85da9aa3c035fc"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90123853fc8b1747f80b0d354be3d122b4365a93e50fc3aacc9fb4c2488845d6"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a61f659665a39a4d17d699ab3593d7116d66e1e2e3f03ef3fb8f484e91908808"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc97f0640e91d7776530f06e6836c546c1c752a52de158720c4224c9e8053cad"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a54e99a2b9693a37ebf245937fd6e9228b4cbd64b9cc961e1f3391ec6c7391"}, + {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4b677d929cf1f6bac07ad76e0f2d5de367e6373351c01a9c0a39f6b21b4a8b"}, + {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5ef00873303d678aaf8b0627e111fd434925ca01c657dbb2641410f1cdaef261"}, + {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:349cb40897fd529ca15317c22c0eab67f5ac5178b5bd2c6adc86172045210acc"}, + {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ddef620e70eaffebed5932ce754d539c0930f676aae6212f8e16cd9743dd365"}, + {file = "rpds_py-0.16.2-cp38-none-win32.whl", hash = "sha256:882ce6e25e585949c3d9f9abd29202367175e0aab3aba0c58c9abbb37d4982ff"}, + {file = "rpds_py-0.16.2-cp38-none-win_amd64.whl", hash = "sha256:f4bd4578e44f26997e9e56c96dedc5f1af43cc9d16c4daa29c771a00b2a26851"}, + {file = "rpds_py-0.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:69ac7ea9897ec201ce68b48582f3eb34a3f9924488a5432a93f177bf76a82a7e"}, + {file = "rpds_py-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a9880b4656efe36ccad41edc66789e191e5ee19a1ea8811e0aed6f69851a82f4"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94cb58c0ba2c62ee108c2b7c9131b2c66a29e82746e8fa3aa1a1effbd3dcf1"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24f7a2eb3866a9e91f4599851e0c8d39878a470044875c49bd528d2b9b88361c"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca57468da2d9a660bcf8961637c85f2fbb2aa64d9bc3f9484e30c3f9f67b1dd7"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccd4e400309e1f34a5095bf9249d371f0fd60f8a3a5c4a791cad7b99ce1fd38d"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80443fe2f7b3ea3934c5d75fb0e04a5dbb4a8e943e5ff2de0dec059202b70a8b"}, + {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d6a9f052e72d493efd92a77f861e45bab2f6be63e37fa8ecf0c6fd1a58fedb0"}, + {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:35953f4f2b3216421af86fd236b7c0c65935936a94ea83ddbd4904ba60757773"}, + {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:981d135c7cdaf6cd8eadae1c950de43b976de8f09d8e800feed307140d3d6d00"}, + {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d0dd7ed2f16df2e129496e7fbe59a34bc2d7fc8db443a606644d069eb69cbd45"}, + {file = "rpds_py-0.16.2-cp39-none-win32.whl", hash = "sha256:703d95c75a72e902544fda08e965885525e297578317989fd15a6ce58414b41d"}, + {file = "rpds_py-0.16.2-cp39-none-win_amd64.whl", hash = "sha256:e93ec1b300acf89730cf27975ef574396bc04edecc358e9bd116fb387a123239"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:44627b6ca7308680a70766454db5249105fa6344853af6762eaad4158a2feebe"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3f91df8e6dbb7360e176d1affd5fb0246d2b88d16aa5ebc7db94fd66b68b61da"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d904c5693e08bad240f16d79305edba78276be87061c872a4a15e2c301fa2c0"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:290a81cfbe4673285cdf140ec5cd1658ffbf63ab359f2b352ebe172e7cfa5bf0"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b634c5ec0103c5cbebc24ebac4872b045cccb9456fc59efdcf6fe39775365bd2"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a297a4d08cc67c7466c873c78039d87840fb50d05473db0ec1b7b03d179bf322"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2e75e17bd0bb66ee34a707da677e47c14ee51ccef78ed6a263a4cc965a072a1"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1b9d9260e06ea017feb7172976ab261e011c1dc2f8883c7c274f6b2aabfe01a"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:162d7cd9cd311c1b0ff1c55a024b8f38bd8aad1876b648821da08adc40e95734"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:9b32f742ce5b57201305f19c2ef7a184b52f6f9ba6871cc042c2a61f0d6b49b8"}, + {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac08472f41ea77cd6a5dae36ae7d4ed3951d6602833af87532b556c1b4601d63"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495a14b72bbe217f2695dcd9b5ab14d4f8066a00f5d209ed94f0aca307f85f6e"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d6b6937ae9eac6d6c0ca3c42774d89fa311f55adff3970fb364b34abde6ed3d"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a61226465bda9283686db8f17d02569a98e4b13c637be5a26d44aa1f1e361c2"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cf6af100ffb5c195beec11ffaa8cf8523057f123afa2944e6571d54da84cdc9"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6df15846ee3fb2e6397fe25d7ca6624af9f89587f3f259d177b556fed6bebe2c"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1be2f033df1b8be8c3167ba3c29d5dca425592ee31e35eac52050623afba5772"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f957d6ab25a78b9e7fc9749d754b98eac825a112b4e666525ce89afcbd9ed5"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:088396c7c70e59872f67462fcac3ecbded5233385797021976a09ebd55961dfe"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4c46ad6356e1561f2a54f08367d1d2e70a0a1bb2db2282d2c1972c1d38eafc3b"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:47713dc4fce213f5c74ca8a1f6a59b622fc1b90868deb8e8e4d993e421b4b39d"}, + {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f811771019f063bbd0aa7bb72c8a934bc13ebacb4672d712fc1639cfd314cccc"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f19afcfc0dd0dca35694df441e9b0f95bc231b512f51bded3c3d8ca32153ec19"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4b682c5775d6a3d21e314c10124599976809455ee67020e8e72df1769b87bc3"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c647ca87fc0ebe808a41de912e9a1bfef9acb85257e5d63691364ac16b81c1f0"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:302bd4983bbd47063e452c38be66153760112f6d3635c7eeefc094299fa400a9"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf721ede3eb7b829e4a9b8142bd55db0bdc82902720548a703f7e601ee13bdc3"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:358dafc89ce3894c7f486c615ba914609f38277ef67f566abc4c854d23b997fa"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad0f59ee3dc35526039f4bc23642d52d5f6616b5f687d846bfc6d0d6d486db0"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cffa76b385dfe1e38527662a302b19ffb0e7f5cf7dd5e89186d2c94a22dd9d0c"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:83640a5d7cd3bff694747d50436b8b541b5b9b9782b0c8c1688931d6ee1a1f2d"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:ed99b4f7179d2111702020fd7d156e88acd533f5a7d3971353e568b6051d5c97"}, + {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4022b9dc620e14f30201a8a73898a873c8e910cb642bcd2f3411123bc527f6ac"}, + {file = "rpds_py-0.16.2.tar.gz", hash = "sha256:781ef8bfc091b19960fc0142a23aedadafa826bc32b433fdfe6fd7f964d7ef44"}, ] [[package]] @@ -2781,29 +2757,29 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "68.2.2" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shellingham" -version = "1.5.3" +version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" files = [ - {file = "shellingham-1.5.3-py2.py3-none-any.whl", hash = "sha256:419c6a164770c9c7cfcaeddfacb3d31ac7a8db0b0f3e9c1287679359734107e9"}, - {file = "shellingham-1.5.3.tar.gz", hash = "sha256:cb4a6fec583535bc6da17b647dd2330cf7ef30239e05d547d99ae3705fd0f7f8"}, + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] [[package]] @@ -2874,8 +2850,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0" -resolved_reference = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0" +reference = "47e95503b90338ad19114239b53326d3fc850eef" +resolved_reference = "47e95503b90338ad19114239b53326d3fc850eef" [[package]] name = "tomli" @@ -2890,13 +2866,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.1" +version = "0.12.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, - {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, ] [[package]] @@ -2912,74 +2888,73 @@ files = [ [[package]] name = "traitlets" -version = "5.11.2" +version = "5.14.1" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, - {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "trie" -version = "2.1.1" +version = "2.2.0" description = "Python implementation of the Ethereum Trie structure" optional = false -python-versions = ">=3.7,<4" +python-versions = ">=3.7, <4" files = [ - {file = "trie-2.1.1-py3-none-any.whl", hash = "sha256:c1a5fc17b37a75008a4517e4f297ad8026dce777eb0eed63ee6335c66d7437b7"}, - {file = "trie-2.1.1.tar.gz", hash = "sha256:1c7fa6f4a3088e083764cf4e32a07a69c672fcf15ad922e03f51158d64a855cf"}, + {file = "trie-2.2.0-py3-none-any.whl", hash = "sha256:b6ad00305722b271cd05c9475e741c92a61f0ca53e6cc4fa9a5591e37eac34ca"}, + {file = "trie-2.2.0.tar.gz", hash = "sha256:117a6f0844eb60f2f68ed45e621886690dacd16343394c1adfb3ff44231725bc"}, ] [package.dependencies] eth-hash = ">=0.1.0" eth-utils = ">=2.0.0" -hexbytes = ">=0.2.0" +hexbytes = ">=0.2.0,<0.4.0" rlp = ">=3" sortedcontainers = ">=2.1.0" [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash (>=0.1.0,<1.0.0)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=6.56.4,<7)", "isort (>=5.10.1)", "pycryptodome", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash (>=0.1.0,<1.0.0)", "hypothesis (>=6.56.4,<7)", "ipython", "pre-commit (>=3.4.0)", "pycryptodome", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)"] test = ["hypothesis (>=6.56.4,<7)", "pycryptodome", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "trove-classifiers" -version = "2023.9.19" +version = "2024.1.8" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2023.9.19.tar.gz", hash = "sha256:3e700af445c802f251ce2b741ee78d2e5dfa5ab8115b933b89ca631b414691c9"}, - {file = "trove_classifiers-2023.9.19-py3-none-any.whl", hash = "sha256:55460364fe248294386d4dfa5d16544ec930493ecc6bd1db07a0d50afb37018e"}, + {file = "trove-classifiers-2024.1.8.tar.gz", hash = "sha256:6e36caf430ff6485c4b57a4c6b364a13f6a898d16b9417c6c37467e59c14b05a"}, + {file = "trove_classifiers-2024.1.8-py3-none-any.whl", hash = "sha256:3c1ff4deb10149c7e39ede6e5bbc107def64362ef1ee7590ec98d71fb92f1b6a"}, ] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "urllib3" -version = "1.26.17" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, - {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] @@ -2989,19 +2964,19 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.24.5" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, - {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -3034,13 +3009,13 @@ test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothe [[package]] name = "wcwidth" -version = "0.2.8" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, - {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] @@ -3056,13 +3031,13 @@ files = [ [[package]] name = "wheel" -version = "0.41.2" +version = "0.42.0" description = "A built-package format for Python" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, - {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, + {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, + {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, ] [package.extras] @@ -3187,4 +3162,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b11f60d84eea1a9d63bb0c5dafd5e0193d75b0a01843d27011084e447fed6e27" +content-hash = "d4ae9aec09eff4011767034570acbb0b6d592603ac7bcb27994f1021ef2f2376" diff --git a/pyproject.toml b/pyproject.toml index 1c2cf98b..3e516941 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "03949fe9e3b1c15b8d88dd169b4f5e44fb64fae0"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "47e95503b90338ad19114239b53326d3fc850eef"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/conftest.py b/tests/conftest.py index 17e882a3..69301639 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,8 @@ import boa import pytest +from tests.constants import DECIMAL_PAIRS, POOL_TYPES, TOKEN_TYPES + pytest_plugins = [ "tests.fixtures.accounts", "tests.fixtures.constants", @@ -13,11 +15,6 @@ "tests.fixtures.tokens", ] -pool_types = {"basic": 0, "meta": 1} -token_types = {"plain": 0, "oracle": 1, "rebasing": 2} -return_types = {"revert": 0, "False": 1, "None": 2} -decimal_types = [(18, 18), (10, 12)] - @pytest.fixture(scope="session", autouse=True) def fast_mode(): @@ -26,17 +23,14 @@ def fast_mode(): def pytest_generate_tests(metafunc): if "pool_type" in metafunc.fixturenames: - pool_type_items = sorted(pool_types.items()) + pool_type_items = sorted(POOL_TYPES.items()) metafunc.parametrize( - "pool_type", - [v for k, v in pool_type_items], - ids=[f"(PoolType={k})" for k, v in pool_type_items], + "pool_type", [v for k, v in pool_type_items], ids=[f"(PoolType={k})" for k, v in pool_type_items] ) if "pool_token_types" in metafunc.fixturenames: items = [ - (k, v) for k, v in token_types.items() - if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") + (k, v) for k, v in TOKEN_TYPES.items() if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") ] combinations = sorted(itertools.combinations_with_replacement(items, 2)) metafunc.parametrize( @@ -47,7 +41,7 @@ def pytest_generate_tests(metafunc): if "metapool_token_type" in metafunc.fixturenames: # for meta pool only 1st coin is selected - token_type_items = sorted(token_types.items()) + token_type_items = sorted(TOKEN_TYPES.items()) metafunc.parametrize( "metapool_token_type", [v for k, v in token_type_items], @@ -56,11 +50,7 @@ def pytest_generate_tests(metafunc): if "initial_decimals" in metafunc.fixturenames: # this is only used in the decimals fixture - metafunc.parametrize( - "initial_decimals", - decimal_types, - ids=[f"(Decimals={i},{j})" for i, j in decimal_types], - ) + metafunc.parametrize("initial_decimals", DECIMAL_PAIRS, ids=[f"(Decimals={i},{j})" for i, j in DECIMAL_PAIRS]) @pytest.fixture(scope="session") diff --git a/tests/constants.py b/tests/constants.py new file mode 100644 index 00000000..06718302 --- /dev/null +++ b/tests/constants.py @@ -0,0 +1,3 @@ +POOL_TYPES = {"basic": 0, "meta": 1} +TOKEN_TYPES = {"plain": 0, "oracle": 1, "rebasing": 2} +DECIMAL_PAIRS = [(18, 18), (10, 12)] diff --git a/tests/factory/test_factory_add_pools.py b/tests/factory/test_factory_add_pools.py index 7d41be68..f4c92d01 100644 --- a/tests/factory/test_factory_add_pools.py +++ b/tests/factory/test_factory_add_pools.py @@ -5,23 +5,19 @@ @pytest.fixture def empty_factory(deployer, fee_receiver, owner): with boa.env.prank(deployer): - _factory = boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) + _factory = boa.load("contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner) return _factory @pytest.fixture def empty_factory_with_implementations( - empty_factory, - owner, - gauge_implementation, - views_implementation, - math_implementation, - amm_implementation, - amm_implementation_meta, + empty_factory, + owner, + gauge_implementation, + views_implementation, + math_implementation, + amm_implementation, + amm_implementation_meta, ): with boa.env.prank(owner): empty_factory.set_gauge_implementation(gauge_implementation.address) @@ -34,14 +30,7 @@ def empty_factory_with_implementations( return empty_factory -def test_add_base_pool_already_exists( - owner, - factory, - add_base_pool, - base_pool, - base_pool_lp_token, - base_pool_tokens, -): +def test_add_base_pool_already_exists(owner, factory, add_base_pool, base_pool, base_pool_lp_token, base_pool_tokens): with boa.reverts(): factory.add_base_pool( base_pool.address, @@ -52,13 +41,7 @@ def test_add_base_pool_already_exists( ) -def test_add_base_pool_only_admin( - factory, - bob, - base_pool, - base_pool_lp_token, - base_pool_tokens, -): +def test_add_base_pool_only_admin(factory, bob, base_pool, base_pool_lp_token, base_pool_tokens): with boa.reverts(): factory.add_base_pool( base_pool.address, @@ -69,9 +52,7 @@ def test_add_base_pool_only_admin( ) -def test_deploy_plain_pool( - empty_factory_with_implementations, amm_interface, plain_tokens, pool_size, zero_address -): +def test_deploy_plain_pool(empty_factory_with_implementations, amm_deployer, plain_tokens, pool_size, zero_address): swap_address = empty_factory_with_implementations.deploy_plain_pool( "test", "test", @@ -87,7 +68,7 @@ def test_deploy_plain_pool( ) assert swap_address != zero_address - swap = amm_interface.at(swap_address) + swap = amm_deployer.at(swap_address) assert swap.coins(0) == plain_tokens[0].address assert swap.coins(1) == plain_tokens[1].address @@ -100,27 +81,27 @@ def test_deploy_plain_pool( def test_pool_count( - empty_factory_with_implementations, - add_base_pool, - amm_interface, - set_pool_implementations, - pool_tokens, - pool_size, - zero_address, + empty_factory_with_implementations, + add_base_pool, + amm_deployer, + set_pool_implementations, + pool_tokens, + pool_size, + zero_address, ): assert empty_factory_with_implementations.pool_count() == 0 empty_factory_with_implementations.deploy_plain_pool( - "test", - "test", - [t.address for t in pool_tokens], - 2000, - 1000000, - 20000000000, - 866, - 0, - [0] * pool_size, - [bytes(b"")] * pool_size, - [zero_address] * pool_size, + "test", # name: String[32] + "test", # symbol: String[10] + [t.address for t in pool_tokens], # coins: address[] + 2000, # A: uint256 + 1000000, # fee: uint256 + 20000000000, # offpeg_fee_multiplier: uint256 + 866, # ma_exp_time: uint256 + 0, # implementation_idx: uint256 + [0] * pool_size, # asset_types: uint8[] + [bytes(b"")] * pool_size, # method_ids: bytes4[] + [zero_address] * pool_size, # oracles: address[] ) assert empty_factory_with_implementations.pool_count() == 1 diff --git a/tests/factory/test_factory_general.py b/tests/factory/test_factory_general.py index 45e96cd8..d3d86c84 100644 --- a/tests/factory/test_factory_general.py +++ b/tests/factory/test_factory_general.py @@ -1,4 +1,4 @@ -def test_get_A(factory, swap): +def test_get_A(factory, swap, set_metapool_implementations): assert factory.get_A(swap.address) == swap.A() diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 8b38646e..62e19423 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -2,68 +2,72 @@ import pytest -@pytest.fixture() -def gauge_interface(): +@pytest.fixture(scope="session") +def gauge_deployer(): return boa.load_partial("contracts/main/LiquidityGauge.vy") -@pytest.fixture() -def gauge_implementation(deployer, gauge_interface): - with boa.env.prank(deployer): - return gauge_interface.deploy_as_blueprint() +@pytest.fixture(scope="session") +def amm_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNG.vy") -@pytest.fixture() -def amm_interface(): - return boa.load_partial("contracts/main/CurveStableSwapNG.vy") +@pytest.fixture(scope="session") +def meta_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") + + +@pytest.fixture(scope="session") +def factory_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapFactoryNG.vy") + + +@pytest.fixture(scope="session") +def views_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNGViews.vy") + + +@pytest.fixture(scope="session") +def math_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNGMath.vy") @pytest.fixture() -def amm_implementation(deployer, amm_interface): +def gauge_implementation(deployer, gauge_deployer): with boa.env.prank(deployer): - impl = amm_interface.deploy_as_blueprint() - return impl + return gauge_deployer.deploy_as_blueprint() @pytest.fixture() -def amm_interface_meta(): - return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") +def amm_implementation(deployer, amm_deployer): + with boa.env.prank(deployer): + return amm_deployer.deploy_as_blueprint() @pytest.fixture() -def amm_implementation_meta(deployer, amm_interface_meta): +def amm_implementation_meta(deployer, meta_deployer): with boa.env.prank(deployer): - impl = amm_interface_meta.deploy_as_blueprint() - return impl + return meta_deployer.deploy_as_blueprint() @pytest.fixture() -def views_implementation(deployer): +def views_implementation(deployer, views_deployer): with boa.env.prank(deployer): - return boa.load("contracts/main/CurveStableSwapNGViews.vy") + return views_deployer.deploy() @pytest.fixture() -def math_implementation(deployer): +def math_implementation(deployer, math_deployer): with boa.env.prank(deployer): - return boa.load("contracts/main/CurveStableSwapNGMath.vy") + return math_deployer.deploy() @pytest.fixture() def factory( - deployer, - fee_receiver, - owner, - gauge_implementation, - views_implementation, - math_implementation, + deployer, fee_receiver, owner, gauge_implementation, views_implementation, math_implementation, factory_deployer ): with boa.env.prank(deployer): - factory = boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) + factory = factory_deployer.deploy(fee_receiver, owner) with boa.env.prank(owner): factory.set_gauge_implementation(gauge_implementation.address) @@ -74,32 +78,23 @@ def factory( # <--------------------- Functions ---------------------> -@pytest.fixture(scope="module") +@pytest.fixture() def set_pool_implementations(owner, factory, amm_implementation): with boa.env.prank(owner): factory.set_pool_implementations(0, amm_implementation.address) -@pytest.fixture(scope="module") +@pytest.fixture() def set_metapool_implementations(owner, factory, amm_implementation_meta): with boa.env.prank(owner): factory.set_metapool_implementations(0, amm_implementation_meta.address) @pytest.fixture() -def add_base_pool( - owner, - factory, - base_pool, - base_pool_lp_token, - base_pool_tokens, -): +def add_base_pool(owner, factory, base_pool, base_pool_lp_token, base_pool_tokens): with boa.env.prank(owner): factory.add_base_pool( - base_pool.address, - base_pool_lp_token.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), + base_pool.address, base_pool_lp_token.address, [0] * len(base_pool_tokens), len(base_pool_tokens) ) @@ -122,7 +117,7 @@ def set_math_implementation(owner, factory, math_implementation): @pytest.fixture() -def gauge(owner, factory, swap, gauge_interface, set_gauge_implementation): +def gauge(owner, factory, swap, gauge_deployer, set_gauge_implementation): with boa.env.prank(owner): gauge_address = factory.deploy_gauge(swap.address) - return gauge_interface.at(gauge_address) + return gauge_deployer.at(gauge_address) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 7b4e93d9..6b0ae89f 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,61 +2,62 @@ import pytest from eth_utils import function_signature_to_4byte_selector -oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") -offpeg_fee_multiplier = 20000000000 +ORACLE_METHOD_ID = function_signature_to_4byte_selector("exchangeRate()") +OFFPEG_FEE_MULTIPLIER = 20000000000 @pytest.fixture() -def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_interface): +def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_deployer, set_pool_implementations): A = 2000 fee = 1000000 - method_ids = [bytes(b"")] * pool_size + method_ids = [b""] * pool_size oracles = [zero_address] * pool_size - asset_types = [] for i, token in enumerate(pool_tokens): - asset_type = token.asset_type() - - if asset_type == 0: - A = 2000 - fee = 1000000 - asset_types.append(asset_type) - elif asset_type == 1: - A = 1000 - fee = 3000000 - asset_types.append(asset_type) - method_ids[i] = oracle_method_id - oracles[i] = token.address - elif asset_type == 2: - A = 500 - fee = 4000000 - asset_types.append(asset_type) + match token.asset_type(): + case 0: # Plain + A = 2000 + fee = 1000000 + case 1: # Oracle + A = 1000 + fee = 3000000 + method_ids[i] = ORACLE_METHOD_ID + oracles[i] = token.address + case 2: # Rebasing + A = 500 + fee = 4000000 with boa.env.prank(deployer): + ma_exp_time = 866 + implementation_idx = 0 + asset_types = [t.asset_type() for t in pool_tokens] + coins = [t.address for t in pool_tokens] + pool = factory.deploy_plain_pool( "test", "test", - [t.address for t in pool_tokens], + coins, A, fee, - offpeg_fee_multiplier, - 866, - 0, + OFFPEG_FEE_MULTIPLIER, + ma_exp_time, + implementation_idx, asset_types, method_ids, oracles, ) - return amm_interface.at(pool) + return amm_deployer.at(pool) @pytest.fixture() -def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface_meta): +def meta_swap( + factory, set_metapool_implementations, zero_address, metapool_token, base_pool, meta_deployer, add_base_pool +): A = 2000 fee = 1000000 method_id = bytes(b"") oracle = zero_address - metapool_token = underlying_tokens[0] asset_type = metapool_token.asset_type() # 0 = Plain, 1 = Oracle, 2 = Rebasing if asset_type == 0: @@ -66,7 +67,7 @@ def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface elif asset_type == 1: A = 1000 fee = 3000000 - method_id = oracle_method_id + method_id = ORACLE_METHOD_ID oracle = metapool_token.address elif asset_type == 2: @@ -80,7 +81,7 @@ def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface metapool_token.address, # _coin: address, A, # _A: uint256, fee, # _fee: uint256, - offpeg_fee_multiplier, + OFFPEG_FEE_MULTIPLIER, 866, # _ma_exp_time: uint256, 0, # _implementation_idx: uint256 asset_type, # _asset_type: uint8 @@ -88,7 +89,7 @@ def meta_swap(factory, zero_address, underlying_tokens, base_pool, amm_interface oracle, # _oracle: address ) - return amm_interface_meta.at(pool) + return meta_deployer.at(pool) @pytest.fixture() @@ -97,17 +98,16 @@ def swap(basic_swap, meta_swap, pool_type): # <--------------------- Metapool configuration ---------------------> +@pytest.fixture(scope="session") +def base_pool_deployer(): + return boa.load_partial("contracts/mocks/CurvePool.vy") + + @pytest.fixture() -def base_pool(deployer, owner, alice, base_pool_decimals, base_pool_tokens, base_pool_lp_token, zero_address): +def base_pool(deployer, owner, alice, base_pool_tokens, base_pool_lp_token, base_pool_deployer): with boa.env.prank(deployer): - base_pool = boa.load( - "contracts/mocks/CurvePool.vy", - owner, - [t.address for t in base_pool_tokens], - base_pool_lp_token.address, - 200, - 3000000, - 5000000000, + base_pool = base_pool_deployer.deploy( + owner, [t.address for t in base_pool_tokens], base_pool_lp_token.address, 200, 3000000, 5000000000 ) base_pool_lp_token.set_minter(base_pool.address) return base_pool diff --git a/tests/gauge/test_rewards.py b/tests/gauge/test_rewards.py index 5bac73f0..48921dc0 100644 --- a/tests/gauge/test_rewards.py +++ b/tests/gauge/test_rewards.py @@ -1,13 +1,13 @@ import boa import pytest -REWARD = 10 ** 20 +REWARD = 10**20 WEEK = 7 * 86400 -LP_AMOUNT = 10 ** 18 +LP_AMOUNT = 10**18 @pytest.fixture(autouse=True) -def initial_setup(forked_chain, owner, gauge, swap, add_initial_liquidity_owner, set_gauge_implementation): +def initial_setup(forked_chain, factory, owner, gauge, swap, add_initial_liquidity_owner): with boa.env.prank(owner): swap.approve(gauge.address, LP_AMOUNT) gauge.deposit(LP_AMOUNT) diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index 778d3ce5..c4b254c2 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -24,23 +24,11 @@ def ng_base_pool_tokens(ng_base_pool_decimals): @pytest.fixture(scope="module") def meta_token(): - return boa.load( - "contracts/mocks/ERC20.vy", - "OTA", - "OTA", - 18, - ) + return boa.load("contracts/mocks/ERC20.vy", "OTA", "OTA", 18) @pytest.fixture(scope="module") -def ng_base_pool( - deployer, - factory, - ng_base_pool_tokens, - zero_address, - amm_interface, - set_pool_implementations, -): +def ng_base_pool(deployer, factory, ng_base_pool_tokens, zero_address, amm_deployer, set_pool_implementations): pool_size = len(ng_base_pool_tokens) offpeg_fee_multiplier = 20000000000 method_ids = [bytes(b"")] * pool_size @@ -63,7 +51,7 @@ def ng_base_pool( oracles, ) - return amm_interface.at(pool) + return amm_deployer.at(pool) @pytest.fixture(scope="module") @@ -72,18 +60,10 @@ def ng_metapool_tokens(meta_token, ng_base_pool): @pytest.fixture(scope="module") -def add_ng_base_pool( - owner, - factory, - ng_base_pool, - ng_base_pool_tokens, -): +def add_ng_base_pool(owner, factory, ng_base_pool, ng_base_pool_tokens): with boa.env.prank(owner): factory.add_base_pool( - ng_base_pool.address, - ng_base_pool.address, - [0] * len(ng_base_pool_tokens), - len(ng_base_pool_tokens), + ng_base_pool.address, ng_base_pool.address, [0] * len(ng_base_pool_tokens), len(ng_base_pool_tokens) ) @@ -94,7 +74,7 @@ def empty_swap( zero_address, meta_token, ng_base_pool, - amm_interface_meta, + meta_deployer, add_ng_base_pool, set_metapool_implementations, ): @@ -120,7 +100,7 @@ def empty_swap( oracle, # _oracle: address ) - return amm_interface_meta.at(pool) + return meta_deployer.at(pool) @pytest.fixture(scope="module") @@ -133,13 +113,7 @@ def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, n @pytest.fixture(scope="module") def deposit_amounts( - meta_token, - ng_base_pool, - ng_base_pool_tokens, - ng_base_pool_decimals, - empty_swap, - bob, - mint_and_approve_for_bob, + meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob ): _deposit_amounts = [] INITIAL_AMOUNT = 1_000_000 * BASE_N_COINS @@ -164,12 +138,7 @@ def swap(empty_swap, bob, deposit_amounts): @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_exchange_underlying_ng_base( - swap, - bob, - sending, - receiving, -): +def test_exchange_underlying_ng_base(swap, bob, sending, receiving): amount = 10**19 expected_out = swap.get_dy_underlying(sending, receiving, amount) actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) diff --git a/tests/pools/meta/test_meta_zap.py b/tests/pools/meta/test_meta_zap.py index 2735b10d..2cffa6c4 100644 --- a/tests/pools/meta/test_meta_zap.py +++ b/tests/pools/meta/test_meta_zap.py @@ -11,12 +11,7 @@ @pytest.fixture(scope="module") def meta_token(deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20.vy", - "OTA", - "OTA", - 18, - ) + return boa.load("contracts/mocks/ERC20.vy", "OTA", "OTA", 18) @pytest.fixture(scope="module") @@ -30,32 +25,16 @@ def tokens_all(meta_token, base_pool_tokens): @pytest.fixture(scope="module") -def add_base_pool( - owner, - factory, - base_pool, - base_pool_lp_token, - base_pool_tokens, -): +def add_base_pool(owner, factory, base_pool, base_pool_lp_token, base_pool_tokens): with boa.env.prank(owner): factory.add_base_pool( - base_pool.address, - base_pool_lp_token.address, - [0] * len(base_pool_tokens), - len(base_pool_tokens), + base_pool.address, base_pool_lp_token.address, [0] * len(base_pool_tokens), len(base_pool_tokens) ) @pytest.fixture(scope="module") def empty_swap( - deployer, - factory, - zero_address, - meta_token, - base_pool, - amm_interface_meta, - add_base_pool, - set_metapool_implementations, + deployer, factory, zero_address, meta_token, base_pool, meta_deployer, add_base_pool, set_metapool_implementations ): method_id = bytes(b"") oracle = zero_address @@ -80,7 +59,7 @@ def empty_swap( oracle, # _oracle: address ) - return amm_interface_meta.at(pool) + return meta_deployer.at(pool) @pytest.fixture(scope="module") @@ -97,7 +76,6 @@ def initial_amts(): @pytest.fixture(scope="module") def swap(zap, base_pool, empty_swap, charlie, tokens_all, initial_amts): - for i in range(3): assert base_pool.balances(i) == 0 @@ -114,7 +92,6 @@ def swap(zap, base_pool, empty_swap, charlie, tokens_all, initial_amts): def test_calc_amts_add(zap, swap, charlie, tokens_all): - deposit_amount = 2 * 100 * 10**18 for token in tokens_all: @@ -132,7 +109,6 @@ def test_calc_amts_add(zap, swap, charlie, tokens_all): def test_calc_amts_remove_imbalance( zap, swap, meta_token, base_pool_tokens, base_pool_lp_token, base_pool, charlie, tokens_all, initial_amts ): - amounts = [i // 4 for i in initial_amts] initial_balance = swap.balanceOf(charlie) swap.approve(zap, 2**256 - 1, sender=charlie) @@ -152,7 +128,6 @@ def test_calc_amts_remove_imbalance( def test_calc_amts_remove(zap, swap, charlie, tokens_all, meta_token, base_pool, base_pool_tokens): - charlie_bal_before = [] for _t in tokens_all: charlie_bal_before.append(_t.balanceOf(charlie)) diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index 99de923a..f686e4b8 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -9,7 +9,6 @@ def mint_vault_tokens(deposit_amount, underlying_token, vault_contract, user): - mint_for_testing(user, deposit_amount, underlying_token, False) underlying_token.approve(vault_contract, 2**256 - 1, sender=user) vault_contract.deposit(deposit_amount, user, sender=user) @@ -17,14 +16,12 @@ def mint_vault_tokens(deposit_amount, underlying_token, vault_contract, user): def donate_to_vault(donation_amount, underlying_token, vault_contract, user): - donation_amount = donation_amount * 10 ** underlying_token.decimals() mint_for_testing(user, donation_amount, underlying_token, False) underlying_token.transfer(vault_contract, donation_amount, sender=user) def mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i): - amount_erc20_in = 10 ** pool_erc20_tokens[i].decimals() if amount_erc20_in > pool_erc20_tokens[i].balanceOf(charlie): @@ -41,58 +38,55 @@ def mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i): @pytest.fixture(scope="module") -def asset(deployer): +def erc20_deployer(): + return boa.load_partial("contracts/mocks/ERC20.vy") + + +@pytest.fixture() +def asset(deployer, erc20_deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20.vy", - "Asset", - "AST", - 8, # 8 decimals - ) + return erc20_deployer.deploy("Asset", "AST", 8) # 8 decimals @pytest.fixture(scope="module") -def token_a(deployer, asset): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC4626.vy", - "Vault", - "VLT", - 18, # 8 decimals - asset.address, - ) +def erc4626_deployer(): + return boa.load_partial("contracts/mocks/ERC4626.vy") @pytest.fixture(scope="module") -def token_b(deployer): - with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20Oracle.vy", - "Oracle", - "ORC", - 18, - 1006470359024000000, - ) +def erc20oracle_deployer(): + return boa.load_partial("contracts/mocks/ERC20Oracle.vy") @pytest.fixture(scope="module") -def token_c(deployer): +def erc20rebasing_conditional_deployer(): + return boa.load_partial("contracts/mocks/ERC20RebasingConditional.vy") + + +@pytest.fixture() +def token_a(deployer, asset, erc4626_deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20RebasingConditional.vy", - "Rebasing", - "RBSN", - 6, - True, - ) + return erc4626_deployer.deploy("Vault", "VLT", 18, asset.address) # 8 decimals -@pytest.fixture(scope="module") +@pytest.fixture() +def token_b(deployer, erc20oracle_deployer): + with boa.env.prank(deployer): + return erc20oracle_deployer.deploy("Oracle", "ORC", 18, 1006470359024000000) + + +@pytest.fixture() +def token_c(deployer, erc20rebasing_conditional_deployer): + with boa.env.prank(deployer): + return erc20rebasing_conditional_deployer.deploy("Rebasing", "RBSN", 6, True) + + +@pytest.fixture() def pool_tokens(token_a, token_b, token_c): return [token_a, token_b, token_c] -@pytest.fixture(scope="module") +@pytest.fixture() def pool_erc20_tokens(asset, token_b, token_c): return [asset, token_b, token_c] @@ -113,15 +107,7 @@ def asset_types(pool_tokens): @pytest.fixture() -def empty_swap( - deployer, - factory, - pool_tokens, - zero_address, - amm_interface, - asset_types, - set_pool_implementations, -): +def empty_swap(deployer, factory, pool_tokens, zero_address, amm_deployer, asset_types, set_pool_implementations): pool_size = len(pool_tokens) oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") offpeg_fee_multiplier = 20000000000 @@ -131,7 +117,6 @@ def empty_swap( fee = 3000000 for i in range(pool_size): - if asset_types[i] == 1: method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address @@ -151,10 +136,10 @@ def empty_swap( oracles, ) - return amm_interface.at(pool) + return amm_deployer.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def deposit_amounts(pool_erc20_tokens, token_a, bob): _deposit_amounts = [] for i, token in enumerate(pool_erc20_tokens): @@ -186,7 +171,6 @@ def swap(empty_swap, bob, deposit_amounts, pool_tokens): @pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): - amount_in = mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i) if "RebasingConditional" in pool_tokens[i].filename: @@ -205,7 +189,6 @@ def test_swap(swap, i, j, charlie, pool_tokens, pool_erc20_tokens): @pytest.mark.parametrize("i,j", itertools.permutations(range(3), 2)) def test_donate_swap(swap, i, j, alice, charlie, pool_tokens, pool_erc20_tokens): - amount_in = mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i) # rebase: @@ -228,7 +211,6 @@ def test_donate_swap(swap, i, j, alice, charlie, pool_tokens, pool_erc20_tokens) def test_rebase(swap, charlie, bob, pool_tokens): - amount_rewards = 10**4 * 10**18 i = 1 if amount_rewards > pool_tokens[i].balanceOf(charlie): diff --git a/tests/pools/test_specific_liquidity_operations.py b/tests/pools/test_specific_liquidity_operations.py index ee19969b..fb127c58 100644 --- a/tests/pools/test_specific_liquidity_operations.py +++ b/tests/pools/test_specific_liquidity_operations.py @@ -8,36 +8,19 @@ @pytest.fixture(scope="module") def token_a(deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20Oracle.vy", - "OTA", - "OTA", - 18, - 1006470359024000000, - ) + return boa.load("contracts/mocks/ERC20Oracle.vy", "OTA", "OTA", 18, 1006470359024000000) @pytest.fixture(scope="module") def token_b(deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20Oracle.vy", - "OTB", - "OTB", - 18, - 1000000000000000000, - ) + return boa.load("contracts/mocks/ERC20Oracle.vy", "OTB", "OTB", 18, 1000000000000000000) @pytest.fixture(scope="module") def token_c(deployer): with boa.env.prank(deployer): - return boa.load( - "contracts/mocks/ERC20.vy", - "OTC", - "OTC", - 18, - ) + return boa.load("contracts/mocks/ERC20.vy", "OTC", "OTC", 18) @pytest.fixture(scope="module") @@ -59,15 +42,7 @@ def asset_types(pool_tokens): @pytest.fixture(scope="module") -def empty_swap( - deployer, - factory, - pool_tokens, - zero_address, - amm_interface, - asset_types, - set_pool_implementations, -): +def empty_swap(deployer, factory, pool_tokens, zero_address, amm_deployer, asset_types, set_pool_implementations): pool_size = len(pool_tokens) oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") offpeg_fee_multiplier = 20000000000 @@ -77,7 +52,6 @@ def empty_swap( fee = 3000000 for i in range(pool_size): - if asset_types[i] == 1: method_ids[i] = oracle_method_id oracles[i] = pool_tokens[i].address @@ -97,7 +71,7 @@ def empty_swap( oracles, ) - return amm_interface.at(pool) + return amm_deployer.at(pool) @pytest.fixture(scope="module") @@ -114,7 +88,6 @@ def deposit_amounts(pool_tokens, bob): @pytest.fixture(scope="module") def swap(empty_swap, bob, deposit_amounts, pool_tokens): - for token in pool_tokens: token.approve(empty_swap, 2**256 - 1, sender=bob) @@ -123,7 +96,6 @@ def swap(empty_swap, bob, deposit_amounts, pool_tokens): def test_swap(swap, charlie, pool_tokens): - amount_in = 10**18 i = 0 j = 1 @@ -135,7 +107,6 @@ def test_swap(swap, charlie, pool_tokens): def test_rebase(swap, charlie, bob, pool_tokens): - amount_rewards = 10**4 * 10**18 i = 1 if amount_rewards > pool_tokens[i].balanceOf(charlie): diff --git a/tests/token/test_token_approve.py b/tests/token/test_token_approve.py index 41ea67a5..249bc3be 100644 --- a/tests/token/test_token_approve.py +++ b/tests/token/test_token_approve.py @@ -7,18 +7,18 @@ @pytest.mark.parametrize("idx", range(4)) -def test_initial_approval_is_zero(swap, alice, accounts, idx): +def test_initial_approval_is_zero(swap, alice, accounts, idx, set_metapool_implementations): assert swap.allowance(alice, accounts[idx]) == 0 def test_approve(swap, alice, bob): - swap.approve(bob, 10 ** 19, sender=alice) - assert swap.allowance(alice, bob) == 10 ** 19 + swap.approve(bob, 10**19, sender=alice) + assert swap.allowance(alice, bob) == 10**19 def test_modify_approve_zero_nonzero(swap, alice, bob): with boa.env.prank(alice): - swap.approve(bob, 10 ** 19) + swap.approve(bob, 10**19) swap.approve(bob, 0) swap.approve(bob, 12345678) @@ -27,29 +27,29 @@ def test_modify_approve_zero_nonzero(swap, alice, bob): def test_revoke_approve(swap, alice, bob): with boa.env.prank(alice): - swap.approve(bob, 10 ** 19) + swap.approve(bob, 10**19) swap.approve(bob, 0) assert swap.allowance(alice, bob) == 0 def test_approve_self(swap, alice): - swap.approve(alice, 10 ** 19, sender=alice) - assert swap.allowance(alice, alice) == 10 ** 19 + swap.approve(alice, 10**19, sender=alice) + assert swap.allowance(alice, alice) == 10**19 def test_only_affects_target(swap, alice, bob): - swap.approve(bob, 10 ** 19, sender=alice) + swap.approve(bob, 10**19, sender=alice) assert swap.allowance(bob, alice) == 0 def test_returns_true(swap, alice, bob): - tx = swap.approve(bob, 10 ** 19, sender=alice) + tx = swap.approve(bob, 10**19, sender=alice) assert tx is True def test_approval_event_fires(alice, bob, swap): - value = 10 ** 19 + value = 10**19 res, events = call_returning_result_and_logs(swap, "approve", bob, value, sender=alice) assert res is True @@ -58,9 +58,9 @@ def test_approval_event_fires(alice, bob, swap): def test_infinite_approval(initial_setup, swap, alice, bob): - swap.approve(bob, 2 ** 256 - 1, sender=alice) - swap.transferFrom(alice, bob, 10 ** 18, sender=bob) - assert swap.allowance(alice, bob) == 2 ** 256 - 1 + swap.approve(bob, 2**256 - 1, sender=alice) + swap.transferFrom(alice, bob, 10**18, sender=bob) + assert swap.allowance(alice, bob) == 2**256 - 1 def permit_class(swap) -> type[EIP712Message]: @@ -77,13 +77,13 @@ class Permit(EIP712Message): spender: "address" # noqa: F821 value: "uint256" # noqa: F821 nonce: "uint256" # noqa: F821 - deadline: "uint256" = 2 ** 256 - 1 # noqa: F821 + deadline: "uint256" = 2**256 - 1 # noqa: F821 return Permit def test_permit(eth_acc, bob, swap): - value = 2 ** 256 - 1 + value = 2**256 - 1 permit = permit_class(swap)(owner=eth_acc.address, spender=bob, value=value, nonce=0) sig = eth_acc.sign_message(permit.signable_message) @@ -92,15 +92,15 @@ def test_permit(eth_acc, bob, swap): "permit", eth_acc.address, bob, - 2 ** 256 - 1, - 2 ** 256 - 1, + 2**256 - 1, + 2**256 - 1, sig.v, to_bytes32(sig.r), to_bytes32(sig.s), sender=bob, ) - assert swap.allowance(eth_acc.address, bob) == 2 ** 256 - 1 + assert swap.allowance(eth_acc.address, bob) == 2**256 - 1 assert res is True assert len(events) == 1 assert repr(events[0]) == f"Approval(owner={eth_acc.address}, spender={bob}, value={value})" @@ -136,7 +136,7 @@ def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: with boa.env.prank(eth_acc.address): mock_contract = boa.loads(src) - permit = permit_class(swap)(owner=mock_contract.address, spender=bob, value=2 ** 256 - 1, nonce=0) + permit = permit_class(swap)(owner=mock_contract.address, spender=bob, value=2**256 - 1, nonce=0) sig = eth_acc.sign_message(permit.signable_message) res, events = call_returning_result_and_logs( @@ -144,13 +144,13 @@ def _recover_signer(_hash: bytes32, _signature: Bytes[65]) -> address: "permit", mock_contract.address, bob, - 2 ** 256 - 1, - 2 ** 256 - 1, + 2**256 - 1, + 2**256 - 1, sig.v, to_bytes32(sig.r), to_bytes32(sig.s), sender=bob, ) - assert swap.allowance(mock_contract.address, bob) == 2 ** 256 - 1 + assert swap.allowance(mock_contract.address, bob) == 2**256 - 1 assert res is True assert len(events) == 1 From 33e4ba9c85e2e2f85584668fe80499d245e9a122 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 15 Jan 2024 15:28:07 +0100 Subject: [PATCH 288/337] Updated linting --- scripts/deploy_infra.py | 13 +-- scripts/deploy_pool.py | 76 ++++----------- scripts/deploy_proxy_admin.py | 8 +- scripts/deployment_utils.py | 8 +- scripts/set_up_base_pools.py | 54 ++--------- scripts/vote_utils.py | 1 - tests/factory/test_factory_forked.py | 6 +- tests/factory/test_factory_meta.py | 16 ++-- tests/factory/test_factory_plain.py | 4 +- tests/fixtures/mocks.py | 1 - tests/fixtures/tokens.py | 20 +--- .../meta/test_exchange_underlying_reverts.py | 10 +- tests/pools/oracle/test_oracle.py | 92 +++++++++---------- tests/pools/test_donation_get_D.py | 1 - tests/pools/test_exchange.py | 1 - tests/pools/test_exchange_received.py | 16 +--- tests/pools/test_fees.py | 16 +--- tests/pools/test_liquidity.py | 7 +- tests/pools/test_oracles.py | 17 +--- tests/pools/test_swap_getters.py | 1 - tests/pools/test_virtual_price.py | 9 +- tests/token/test_get_D.py | 1 - tests/token/test_token_transfer.py | 3 +- tests/token/test_token_transfer_from.py | 3 +- tests/utils/__init__.py | 1 - 25 files changed, 104 insertions(+), 281 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index be0c694c..8026a51e 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -139,7 +139,6 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: - with open(contract_file, "r") as f: source = f.read() @@ -159,7 +158,6 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): - deployed_contract = deployments[network][contract_designation] if not deployed_contract: @@ -180,7 +178,6 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo def deploy_infra(network, url, account, fork=False): - logger.log(f"Deploying on {network} ...") if fork: @@ -192,9 +189,7 @@ def deploy_infra(network, url, account, fork=False): boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) for _network, data in deploy_utils.curve_dao_network_settings.items(): - if _network in network: - owner = data.dao_ownership_contract fee_receiver = data.fee_receiver_address @@ -252,13 +247,7 @@ def deploy_infra(network, url, account, fork=False): def main(): - - deploy_infra( - "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], - "FIDDYDEPLOYER", - fork=False, - ) + deploy_infra("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False) if __name__ == "__main__": diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 885d6946..3965f70c 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -15,63 +15,27 @@ deployments = { # Ethereum - "ethereum:sepolia": { - "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", - }, - "ethereum:mainnet": { - "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - }, + "ethereum:sepolia": {"factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81"}, + "ethereum:mainnet": {"factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf"}, # Layer 2 - "arbitrum:mainnet": { - "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", - }, - "optimism:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "base:mainnet": { - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - }, - "linea:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "scroll:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "zksync:mainnet": { - "factory": "", - }, - "pzkevm:mainnet": { - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - }, + "arbitrum:mainnet": {"factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b"}, + "optimism:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "base:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, + "linea:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "scroll:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "zksync:mainnet": {"factory": ""}, + "pzkevm:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, "mantle:mainnet": {"factory": ""}, # Layer 1 - "gnosis:mainnet": { - "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - }, - "polygon:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "avax:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "ftm:mainnet": { - "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", - }, - "bsc:mainnet": { - "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - }, - "celo:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "kava:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "aurora:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "tron:mainnet": { - "factory": "", - }, + "gnosis:mainnet": {"factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8"}, + "polygon:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "avax:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "ftm:mainnet": {"factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b"}, + "bsc:mainnet": {"factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B"}, + "celo:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "kava:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "aurora:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "tron:mainnet": {"factory": ""}, } @@ -130,7 +94,6 @@ class PoolSettings: def deploy_pool(network, url, account, pool_type, fork): - logger.log(f"Deploying pool on {network} ...") if fork: @@ -158,7 +121,6 @@ def deploy_pool(network, url, account, pool_type, fork): def deploy_gauge(network, url, account, pool_addr, fork): - logger.log(f"Deploying gauge for pool {pool_addr} on {network} ...") if fork: @@ -181,7 +143,6 @@ def deploy_gauge(network, url, account, pool_addr, fork): def deploy_pool_and_gauge(network, url, account, pool_type, fork): - logger.log(f"Deploying pool on {network} ...") if fork: @@ -211,7 +172,6 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): - fork = False deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090/", "FIDDYDEPLOYER", "meta", fork) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index ca607c85..fca78790 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -13,7 +13,6 @@ def deploy_proxy_admin(network, url, account, fork=False): - logger.log(f"Deploying ProxyAdmin for {network} ...") if fork: @@ -35,12 +34,7 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): - deploy_proxy_admin( - ":mainnet", - os.environ["RPC_"], - "", - fork=False, - ) + deploy_proxy_admin(":mainnet", os.environ["RPC_"], "", fork=False) if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index b7af3632..84ff7a58 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -58,8 +58,7 @@ class CurveNetworkSettings: fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "zksync:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1", + dao_ownership_contract="", fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1" ), "pzkevm:mainnet": CurveNetworkSettings( dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", @@ -98,10 +97,7 @@ class CurveNetworkSettings: dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), - "tron:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), + "tron:mainnet": CurveNetworkSettings(dao_ownership_contract="", fee_receiver_address=""), "mantle:mainnet": CurveNetworkSettings( dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", diff --git a/scripts/set_up_base_pools.py b/scripts/set_up_base_pools.py index 06d127ed..bcb57086 100644 --- a/scripts/set_up_base_pools.py +++ b/scripts/set_up_base_pools.py @@ -75,20 +75,14 @@ class BasePoolSettings: BasePoolSettings( # 2pool pool="0x7f90122BF0700F9E7e1F688fe926940E8839F353", lp_token="0x7f90122BF0700F9E7e1F688fe926940E8839F353", - coins=[ - "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", - "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", - ], + coins=["0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"], asset_types=[0, 0], n_coins=2, ), BasePoolSettings( # fraxbp pool="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", lp_token="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", - coins=[ - "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", - "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", - ], + coins=["0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"], asset_types=[0, 0], n_coins=2, ), @@ -108,10 +102,7 @@ class BasePoolSettings: BasePoolSettings( # fraxbp pool="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", lp_token="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", - coins=[ - "0x2E3D870790dC77A83DD1d18184Acc7439A53f475", - "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", - ], + coins=["0x2E3D870790dC77A83DD1d18184Acc7439A53f475", "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"], asset_types=[0, 0], n_coins=2, ), @@ -127,13 +118,12 @@ class BasePoolSettings: ], asset_types=[0, 0, 0], n_coins=3, - ), + ) ], } def set_up_base_pools(network, url, account, fork: bool = False): - logger.log(f"Connecting to {network} ...") if fork: boa.env.fork(url) @@ -154,12 +144,7 @@ def set_up_base_pools(network, url, account, fork: bool = False): if base_pool_data: # check if network has base pools: for data in base_pool_data: if to_checksum_address(data.pool) not in onboarded_base_pools: - factory.add_base_pool( - data.pool, - data.lp_token, - data.asset_types, - data.n_coins, - ) + factory.add_base_pool(data.pool, data.lp_token, data.asset_types, data.n_coins) logger.log(f"Added {data.pool} to factory {factory_address} on {network}.") else: logger.log(f"{data.pool} is already configured as a base pool in factory {factory_address}.") @@ -170,33 +155,12 @@ def set_up_base_pools(network, url, account, fork: bool = False): def main(): - fork = False - set_up_base_pools( - "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "arbitrum:mainnet", - os.environ["RPC_ARBITRUM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "optimism:mainnet", - os.environ["RPC_OPTIMISM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "gnosis:mainnet", - os.environ["RPC_GNOSIS"], - "FIDDYDEPLOYER", - fork=fork, - ) + set_up_base_pools("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("arbitrum:mainnet", os.environ["RPC_ARBITRUM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("optimism:mainnet", os.environ["RPC_OPTIMISM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("gnosis:mainnet", os.environ["RPC_GNOSIS"], "FIDDYDEPLOYER", fork=fork) if __name__ == "__main__": diff --git a/scripts/vote_utils.py b/scripts/vote_utils.py index fd7defe7..b9043e9a 100644 --- a/scripts/vote_utils.py +++ b/scripts/vote_utils.py @@ -47,7 +47,6 @@ def prepare_evm_script(target: Dict, actions: List[Tuple]) -> str: evm_script = "0x00000001" for address, fn_name, *args in actions: - contract = ape.Contract(address) fn = getattr(contract, fn_name) calldata = fn.as_transaction(*args, sender=agent.address, gas_price=0).data diff --git a/tests/factory/test_factory_forked.py b/tests/factory/test_factory_forked.py index 7f51ea3b..6ba2e788 100644 --- a/tests/factory/test_factory_forked.py +++ b/tests/factory/test_factory_forked.py @@ -5,11 +5,7 @@ @pytest.fixture def empty_factory(deployer, fee_receiver, owner): with boa.env.prank(deployer): - return boa.load( - "contracts/main/CurveStableSwapFactoryNG.vy", - fee_receiver, - owner, - ) + return boa.load("contracts/main/CurveStableSwapFactoryNG.vy", fee_receiver, owner) def test_add_base_pool(empty_factory, owner, forked_chain): diff --git a/tests/factory/test_factory_meta.py b/tests/factory/test_factory_meta.py index 819c5539..0820f375 100644 --- a/tests/factory/test_factory_meta.py +++ b/tests/factory/test_factory_meta.py @@ -9,8 +9,8 @@ @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_find_pool_for_coins(factory, meta_swap, underlying_tokens, sending, receiving): assert ( - factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) - == meta_swap.address + factory.find_pool_for_coins(underlying_tokens[sending].address, underlying_tokens[receiving].address) + == meta_swap.address ) @@ -34,25 +34,21 @@ def test_get_underlying_decimals(factory, meta_swap, base_pool_decimals, pool_ty def test_get_metapool_rates(factory, meta_swap, base_pool, initial_setup): - assert factory.get_metapool_rates(meta_swap.address) == [10 ** 18, base_pool.get_virtual_price()] + assert factory.get_metapool_rates(meta_swap.address) == [10**18, base_pool.get_virtual_price()] def test_get_underlying_balances(factory, meta_swap, base_pool, initial_setup): - assert factory.get_metapool_rates(meta_swap.address) == [10 ** 18, base_pool.get_virtual_price()] + assert factory.get_metapool_rates(meta_swap.address) == [10**18, base_pool.get_virtual_price()] @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) -def test_find_pool_underlying_base_pool_only( - self, factory, underlying_tokens, sending, receiving, zero_address -): +def test_find_pool_underlying_base_pool_only(self, factory, underlying_tokens, sending, receiving, zero_address): assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(2, 5), 2)) def test_get_coin_indices_underlying(factory, meta_swap, sending, receiving, underlying_tokens): - i, j, is_underlying = factory.get_coin_indices( - meta_swap, underlying_tokens[sending], underlying_tokens[receiving] - ) + i, j, is_underlying = factory.get_coin_indices(meta_swap, underlying_tokens[sending], underlying_tokens[receiving]) assert i == sending - 1 assert j == receiving - 1 assert is_underlying is True diff --git a/tests/factory/test_factory_plain.py b/tests/factory/test_factory_plain.py index 98a4c213..f4056ce1 100644 --- a/tests/factory/test_factory_plain.py +++ b/tests/factory/test_factory_plain.py @@ -5,8 +5,8 @@ @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_find_pool_for_coins(factory, basic_swap, plain_tokens, sending, receiving): assert ( - factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) - == basic_swap.address + factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) + == basic_swap.address ) diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py index 7555d1cb..325df5b2 100644 --- a/tests/fixtures/mocks.py +++ b/tests/fixtures/mocks.py @@ -4,7 +4,6 @@ @pytest.fixture(scope="module") def callback_contract(bob, swap, pool_tokens, underlying_tokens): - with boa.env.prank(bob): _callback = boa.load("contracts/mocks/CallbackSwap.vy", swap.address, bob) for token in pool_tokens + underlying_tokens: diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 86964f0b..b5754176 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -15,24 +15,8 @@ def plain_tokens(deployer, decimals): def oracle_tokens(deployer, decimals): tokens = [] with boa.env.prank(deployer): - tokens.append( - boa.load( - "contracts/mocks/ERC20Oracle.vy", - "OTA", - "OTA", - 18, - 1006470359024000000, - ) - ) - tokens.append( - boa.load( - "contracts/mocks/ERC20Oracle.vy", - "OTB", - "OTB", - 18, - 1007580460035000000, - ) - ) + tokens.append(boa.load("contracts/mocks/ERC20Oracle.vy", "OTA", "OTA", 18, 1006470359024000000)) + tokens.append(boa.load("contracts/mocks/ERC20Oracle.vy", "OTB", "OTB", 18, 1007580460035000000)) return tokens diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py index 39c24e2b..ddd621ba 100644 --- a/tests/pools/meta/test_exchange_underlying_reverts.py +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -20,7 +20,7 @@ def test_min_dy_too_high(bob, meta_swap, underlying_tokens, meta_decimals, base_ @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) def test_insufficient_balance( - bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address + bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address ): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] @@ -39,25 +39,25 @@ def test_same_coin(bob, meta_swap, idx): meta_swap.exchange_underlying(idx, idx, 0, 0, sender=bob) -@pytest.mark.parametrize("idx", [-1, -(2 ** 127)]) +@pytest.mark.parametrize("idx", [-1, -(2**127)]) def test_i_below_zero(bob, meta_swap, idx): with boa.reverts(): meta_swap.exchange_underlying(idx, 0, 0, 0, sender=bob) -@pytest.mark.parametrize("idx", [4, 2 ** 127 - 1]) +@pytest.mark.parametrize("idx", [4, 2**127 - 1]) def test_i_above_n_coins(bob, meta_swap, idx): with boa.reverts(): meta_swap.exchange_underlying(idx, 0, 0, 0, sender=bob) -@pytest.mark.parametrize("idx", [-1, -(2 ** 127)]) +@pytest.mark.parametrize("idx", [-1, -(2**127)]) def test_j_below_zero(bob, meta_swap, idx): with boa.reverts(): meta_swap.exchange_underlying(0, idx, 0, 0, sender=bob) -@pytest.mark.parametrize("idx", [4, 2 ** 127 - 1]) +@pytest.mark.parametrize("idx", [4, 2**127 - 1]) def test_j_above_n_coins(bob, meta_swap, idx): with boa.reverts(): meta_swap.exchange_underlying(0, idx, 0, 0, sender=bob) diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index 24108a2c..b38ab1e4 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -10,27 +10,27 @@ @pytest.fixture(scope="module") def initial_setup_alice( - alice, - deposit_amounts, - swap, - pool_type, - base_pool, - base_oracle_tokens, - base_pool_decimals, - base_pool_lp_token, - initial_balance, - initial_amounts, - oracle_tokens, - underlying_tokens, + alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_oracle_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_balance, + initial_amounts, + oracle_tokens, + underlying_tokens, ): with boa.env.anchor(): - mint_for_testing(alice, 1 * 10 ** 18, None, True) + mint_for_testing(alice, 1 * 10**18, None, True) if pool_type == 0: mint_account(alice, oracle_tokens, initial_balance, initial_amounts) with boa.env.prank(alice): for token in oracle_tokens: - token.approve(swap.address, 2 ** 256 - 1) + token.approve(swap.address, 2**256 - 1) else: add_base_pool_liquidity(alice, base_pool, base_oracle_tokens, base_pool_decimals) @@ -38,22 +38,22 @@ def initial_setup_alice( with boa.env.prank(alice): for token in underlying_tokens: - token.approve(swap.address, 2 ** 256 - 1) + token.approve(swap.address, 2**256 - 1) yield def test_initial_liquidity( - alice, - initial_setup_alice, - swap, - pool_type, - pool_token_types, - metapool_token_type, - decimals, - meta_decimals, - oracle_tokens, - metapool_token, + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + oracle_tokens, + metapool_token, ): amounts = [] @@ -62,15 +62,15 @@ def test_initial_liquidity( if t != 1: amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10 ** 18 // oracle_tokens[i].exchangeRate()) + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate()) else: if metapool_token_type == 1: amounts = [ - DEPOSIT_AMOUNT * 10 ** meta_decimals * 10 ** 18 // metapool_token.exchangeRate(), - DEPOSIT_AMOUNT * 10 ** 18, + DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10**18, ] else: - amounts = [DEPOSIT_AMOUNT * 10 ** meta_decimals, DEPOSIT_AMOUNT * 10 ** 18] + amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] swap.add_liquidity(amounts, 0, sender=alice) swap.add_liquidity(amounts, 0, sender=alice) @@ -84,16 +84,16 @@ def test_oracles(alice, swap, pool_size, pool_type, pool_token_types, metapool_t def test_get_dy( - alice, - initial_setup_alice, - swap, - pool_type, - pool_token_types, - metapool_token_type, - decimals, - meta_decimals, - oracle_tokens, - metapool_token, + alice, + initial_setup_alice, + swap, + pool_type, + pool_token_types, + metapool_token_type, + decimals, + meta_decimals, + oracle_tokens, + metapool_token, ): amounts = [] @@ -102,25 +102,25 @@ def test_get_dy( if t != 1: amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10 ** 18 // oracle_tokens[i].exchangeRate()) + amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate()) else: if metapool_token_type == 1: amounts = [ - DEPOSIT_AMOUNT * 10 ** meta_decimals * 10 ** 18 // metapool_token.exchangeRate(), - DEPOSIT_AMOUNT * 10 ** 18, + DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), + DEPOSIT_AMOUNT * 10**18, ] else: - amounts = [DEPOSIT_AMOUNT * 10 ** meta_decimals, DEPOSIT_AMOUNT * 10 ** 18] + amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] swap.add_liquidity(amounts, 0, sender=alice) if pool_type == 0: - rate_1 = 10 ** 18 if pool_token_types[0] != 1 else oracle_tokens[0].exchangeRate() - rate_2 = 10 ** 18 if pool_token_types[1] != 1 else oracle_tokens[1].exchangeRate() + rate_1 = 10**18 if pool_token_types[0] != 1 else oracle_tokens[0].exchangeRate() + rate_2 = 10**18 if pool_token_types[1] != 1 else oracle_tokens[1].exchangeRate() assert swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) else: rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() - assert swap.get_dy(0, 1, 10 ** 18) == pytest.approx(rate_1, rel=1e-3) + assert swap.get_dy(0, 1, 10**18) == pytest.approx(rate_1, rel=1e-3) diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/test_donation_get_D.py index 12f87c2c..2aa84334 100644 --- a/tests/pools/test_donation_get_D.py +++ b/tests/pools/test_donation_get_D.py @@ -5,7 +5,6 @@ def test_donate_get_D(bob, basic_swap, underlying_tokens, pool_tokens): - # check if pool is empty: for i in range(basic_swap.N_COINS()): assert basic_swap.balances(i) == 0 diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py index 90c55839..2b0308f2 100644 --- a/tests/pools/test_exchange.py +++ b/tests/pools/test_exchange.py @@ -91,7 +91,6 @@ class TestExchangeReverts: def test_insufficient_balance( self, charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals ): - amount = 10 ** decimals[sending] for token in pool_tokens + underlying_tokens: diff --git a/tests/pools/test_exchange_received.py b/tests/pools/test_exchange_received.py index 26f831a2..fa642c9a 100644 --- a/tests/pools/test_exchange_received.py +++ b/tests/pools/test_exchange_received.py @@ -22,15 +22,12 @@ def transfer_and_swap( base_pool_decimals, ): def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): - # get input and output tokens: sending_token = "swap" receiving_token = "swap" if pool_type == 1: - if underlying: - if sending == 0: input_coin = underlying_tokens[0] else: @@ -45,12 +42,10 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): receiving_token = "base_pool" else: - input_coin = underlying_tokens[sending] output_coin = underlying_tokens[receiving] else: - input_coin = pool_tokens[sending] output_coin = pool_tokens[receiving] @@ -105,14 +100,7 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) @pytest.mark.skip_rebasing_tokens -def test_exchange_received_nonrebasing( - bob, - swap, - pool_tokens, - sending, - receiving, - transfer_and_swap, -): +def test_exchange_received_nonrebasing(bob, swap, pool_tokens, sending, receiving, transfer_and_swap): swap_data = transfer_and_swap(swap, sending, receiving, False) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] @@ -132,7 +120,6 @@ def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving): @pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, receiving, transfer_and_swap): - mint_for_testing(bob, 1, pool_tokens[sending], False) pool_tokens[sending].transfer(swap, 1, sender=bob) @@ -143,7 +130,6 @@ def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, rece @pytest.mark.contains_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): - if 2 in get_asset_types_in_pool(swap): with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/pools/test_fees.py b/tests/pools/test_fees.py index b6576715..2187d367 100644 --- a/tests/pools/test_fees.py +++ b/tests/pools/test_fees.py @@ -9,13 +9,7 @@ def test_admin_balances( self, bob, swap, pool_type, pool_tokens, underlying_tokens, initial_amounts, sending, receiving ): for send, recv in [(sending, receiving), (receiving, sending)]: - swap.exchange( - send, - recv, - initial_amounts[send], - 0, - sender=bob, - ) + swap.exchange(send, recv, initial_amounts[send], 0, sender=bob) for i in (sending, receiving): if pool_type == 0: @@ -39,13 +33,7 @@ def test_withdraw_one_coin( receiving, initial_amounts, ): - swap.exchange( - sending, - receiving, - initial_amounts[sending], - 0, - sender=bob, - ) + swap.exchange(sending, receiving, initial_amounts[sending], 0, sender=bob) admin_balance = swap.admin_balances(receiving) diff --git a/tests/pools/test_liquidity.py b/tests/pools/test_liquidity.py index b26cb192..ba2aaf3b 100644 --- a/tests/pools/test_liquidity.py +++ b/tests/pools/test_liquidity.py @@ -170,7 +170,6 @@ def initial_setup_alice( mint_for_testing(alice, 1 * 10**18, None, True) if pool_type == 0: - mint_account(alice, pool_tokens, initial_balance, initial_amounts) with boa.env.prank(alice): for token in pool_tokens: @@ -203,11 +202,7 @@ def test_initial( deposit_amounts, initial_amounts, ): - swap.add_liquidity( - deposit_amounts, - len(pool_tokens) * min_amount, - sender=alice, - ) + swap.add_liquidity(deposit_amounts, len(pool_tokens) * min_amount, sender=alice) token_types = pool_token_types if pool_type == 0 else [metapool_token_type, 18] diff --git a/tests/pools/test_oracles.py b/tests/pools/test_oracles.py index 960c0087..28473f1b 100644 --- a/tests/pools/test_oracles.py +++ b/tests/pools/test_oracles.py @@ -14,7 +14,6 @@ def get_D(swap, math): - _rates = swap.stored_rates() _balances = swap.internal._balances() xp = swap.internal._xp_mem(_rates, _balances) @@ -26,7 +25,6 @@ def check_oracle(swap, dt): # amm prices: p_amm = [] for n in range(swap.N_COINS() - 1): - _p = swap.get_p(n) assert approx(swap.last_price(n), _p, 1e-5) @@ -42,17 +40,13 @@ def check_oracle(swap, dt): # check: for n in range(swap.N_COINS() - 1): - p1 = int(10**18 * w + p_amm[n] * (1 - w)) assert approx(swap.price_oracle(n), p1, 1e-5) -@given( - amount=strategy("uint256", min_value=1, max_value=10**6), -) +@given(amount=strategy("uint256", min_value=1, max_value=10**6)) @settings(**SETTINGS) def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: @@ -69,7 +63,6 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): p_numeric = [] stored_rates = swap.stored_rates() for n in range(1, swap.N_COINS()): - expected_jth_out = views_implementation.get_dy(0, n, 10**18, swap) p_numeric.append(stored_rates[0] / expected_jth_out) @@ -90,7 +83,6 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): ) @settings(**SETTINGS) def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: @@ -112,7 +104,6 @@ def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, ) @settings(**SETTINGS) def test_price_ema_remove_one(swap, alice, amount, dt0, dt): - i = random.sample(range(swap.N_COINS()), 1)[0] alice_lp_bal = swap.balanceOf(alice) amt_to_remove = int(alice_lp_bal * amount / (10**5 - 1)) @@ -130,7 +121,6 @@ def test_price_ema_remove_one(swap, alice, amount, dt0, dt): ) @settings(**SETTINGS) def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amounts, frac): - i = random.sample(range(swap.N_COINS()), 1)[0] amounts = [0] * pool_size amounts[i] = deposit_amounts[i] // frac @@ -142,9 +132,7 @@ def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amo check_oracle(swap, dt) -@given( - amount=strategy("uint256", min_value=10**9, max_value=10**15), -) +@given(amount=strategy("uint256", min_value=10**9, max_value=10**15)) @settings(**SETTINGS) def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimals, amount): # calc amount in: @@ -176,7 +164,6 @@ def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimal ) @settings(**SETTINGS) def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt, math_implementation): - i, j = random.sample(range(swap.N_COINS()), 2) # calc amount in: diff --git a/tests/pools/test_swap_getters.py b/tests/pools/test_swap_getters.py index e4535c6c..771e1103 100644 --- a/tests/pools/test_swap_getters.py +++ b/tests/pools/test_swap_getters.py @@ -12,7 +12,6 @@ ) @settings(**SETTINGS) def test_get_dx(i, j, amount_in, swap, factory, initial_setup): - n_coins = swap.N_COINS() if i == j or max(i, j) >= n_coins: return diff --git a/tests/pools/test_virtual_price.py b/tests/pools/test_virtual_price.py index 78c22783..28382a4b 100644 --- a/tests/pools/test_virtual_price.py +++ b/tests/pools/test_virtual_price.py @@ -53,19 +53,12 @@ def test_exchange(bob, swap, sending, receiving, decimals): virtual_price = swap.get_virtual_price() amount = 10_000 * 10 ** decimals[sending] - swap.exchange( - sending, - receiving, - amount, - 0, - sender=bob, - ) + swap.exchange(sending, receiving, amount, 0, sender=bob) assert swap.get_virtual_price() > virtual_price def test_donate_virtual_price(bob, swap, pool_tokens, initial_amounts, pool_size): - # make a deposit for i, amount in enumerate(initial_amounts): amounts = [0] * pool_size diff --git a/tests/token/test_get_D.py b/tests/token/test_get_D.py index 999c0fe0..422fd8d8 100644 --- a/tests/token/test_get_D.py +++ b/tests/token/test_get_D.py @@ -4,7 +4,6 @@ @pytest.fixture(scope="module") def new_math(): - return boa.loads( """ A_PRECISION: constant(uint256) = 100 diff --git a/tests/token/test_token_transfer.py b/tests/token/test_token_transfer.py index 0adecfae..2e1d6585 100644 --- a/tests/token/test_token_transfer.py +++ b/tests/token/test_token_transfer.py @@ -5,7 +5,8 @@ @pytest.fixture(autouse=True) -def added_liquidity(initial_setup): ... +def added_liquidity(initial_setup): + ... def test_sender_balance_decreases(alice, bob, swap): diff --git a/tests/token/test_token_transfer_from.py b/tests/token/test_token_transfer_from.py index e7f310b5..6505852f 100644 --- a/tests/token/test_token_transfer_from.py +++ b/tests/token/test_token_transfer_from.py @@ -5,7 +5,8 @@ @pytest.fixture(autouse=True) -def added_liquidity(initial_setup): ... +def added_liquidity(initial_setup): + ... def test_sender_balance_decreases(alice, bob, charlie, swap): diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index db30f6eb..8ef59ad0 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -17,7 +17,6 @@ def approx(x1: int, x2: int, precision: int, abs_precision=None): def get_asset_types_in_pool(pool): - if "asset_type" in pool._immutables.__dict__.keys(): return [pool._immutables.asset_type] return pool._immutables.asset_types From 06aa427c5fb1e0f2ad7161fc17ec3ec68259d91e Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 15 Jan 2024 16:20:41 +0100 Subject: [PATCH 289/337] Module for contract loading --- tests/conftest.py | 1 + tests/fixtures/constants.py | 4 +- tests/fixtures/contracts.py | 77 +++++++++++++++++++ tests/fixtures/factory.py | 30 -------- tests/fixtures/mocks.py | 8 +- tests/fixtures/pools.py | 5 -- tests/fixtures/tokens.py | 58 ++++++-------- tests/pools/meta/test_meta_new_ng_base.py | 18 ++--- tests/pools/meta/test_meta_zap.py | 10 +-- tests/pools/test_erc4626_swaps.py | 22 +----- .../test_specific_liquidity_operations.py | 12 +-- 11 files changed, 126 insertions(+), 119 deletions(-) create mode 100644 tests/fixtures/contracts.py diff --git a/tests/conftest.py b/tests/conftest.py index 69301639..0323a59d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ pytest_plugins = [ "tests.fixtures.accounts", "tests.fixtures.constants", + "tests.fixtures.contracts", "tests.fixtures.factory", "tests.fixtures.mocks", "tests.fixtures.pools", diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 720eb36b..66ff1091 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -3,7 +3,7 @@ INITIAL_AMOUNT = 3_000_000 -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def initial_balance() -> int: return INITIAL_AMOUNT * 10**18 @@ -43,6 +43,6 @@ def deposit_amounts( return amounts -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def zero_address() -> str: return "0x0000000000000000000000000000000000000000" diff --git a/tests/fixtures/contracts.py b/tests/fixtures/contracts.py new file mode 100644 index 00000000..666c35e8 --- /dev/null +++ b/tests/fixtures/contracts.py @@ -0,0 +1,77 @@ +import boa +import pytest + + +@pytest.fixture(scope="session") +def base_pool_deployer(): + return boa.load_partial("contracts/mocks/CurvePool.vy") + + +@pytest.fixture(scope="session") +def erc20_deployer(): + return boa.load_partial("contracts/mocks/ERC20.vy") + + +@pytest.fixture(scope="session") +def erc20_rebasing_deployer(): + return boa.load_partial("contracts/mocks/ERC20Rebasing.vy") + + +@pytest.fixture(scope="session") +def erc4626_deployer(): + return boa.load_partial("contracts/mocks/ERC4626.vy") + + +@pytest.fixture(scope="session") +def erc20oracle_deployer(): + return boa.load_partial("contracts/mocks/ERC20Oracle.vy") + + +@pytest.fixture(scope="session") +def erc20rebasing_conditional_deployer(): + return boa.load_partial("contracts/mocks/ERC20RebasingConditional.vy") + + +@pytest.fixture(scope="session") +def curve_token_v3_deployer(): + return boa.load_partial("contracts/mocks/CurveTokenV3.vy") + + +@pytest.fixture(scope="session") +def zap_deployer(): + return boa.load_partial("contracts/mocks/Zap.vy") + + +@pytest.fixture(scope="session") +def gauge_deployer(): + return boa.load_partial("contracts/main/LiquidityGauge.vy") + + +@pytest.fixture(scope="session") +def amm_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNG.vy") + + +@pytest.fixture(scope="session") +def meta_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") + + +@pytest.fixture(scope="session") +def factory_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapFactoryNG.vy") + + +@pytest.fixture(scope="session") +def views_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNGViews.vy") + + +@pytest.fixture(scope="session") +def math_deployer(): + return boa.load_partial("contracts/main/CurveStableSwapNGMath.vy") + + +@pytest.fixture(scope="session") +def callback_swap_deployer(): + return boa.load_partial("contracts/mocks/CallbackSwap.vy") diff --git a/tests/fixtures/factory.py b/tests/fixtures/factory.py index 62e19423..72d47672 100644 --- a/tests/fixtures/factory.py +++ b/tests/fixtures/factory.py @@ -2,36 +2,6 @@ import pytest -@pytest.fixture(scope="session") -def gauge_deployer(): - return boa.load_partial("contracts/main/LiquidityGauge.vy") - - -@pytest.fixture(scope="session") -def amm_deployer(): - return boa.load_partial("contracts/main/CurveStableSwapNG.vy") - - -@pytest.fixture(scope="session") -def meta_deployer(): - return boa.load_partial("contracts/main/CurveStableSwapMetaNG.vy") - - -@pytest.fixture(scope="session") -def factory_deployer(): - return boa.load_partial("contracts/main/CurveStableSwapFactoryNG.vy") - - -@pytest.fixture(scope="session") -def views_deployer(): - return boa.load_partial("contracts/main/CurveStableSwapNGViews.vy") - - -@pytest.fixture(scope="session") -def math_deployer(): - return boa.load_partial("contracts/main/CurveStableSwapNGMath.vy") - - @pytest.fixture() def gauge_implementation(deployer, gauge_deployer): with boa.env.prank(deployer): diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py index 325df5b2..723c7765 100644 --- a/tests/fixtures/mocks.py +++ b/tests/fixtures/mocks.py @@ -3,10 +3,10 @@ @pytest.fixture(scope="module") -def callback_contract(bob, swap, pool_tokens, underlying_tokens): +def callback_contract(bob, swap, pool_tokens, underlying_tokens, callback_swap_deployer): with boa.env.prank(bob): - _callback = boa.load("contracts/mocks/CallbackSwap.vy", swap.address, bob) + callback = callback_swap_deployer.deploy(swap.address, bob) for token in pool_tokens + underlying_tokens: - token.approve(_callback.address, 2**256 - 1) + token.approve(callback.address, 2**256 - 1) - return _callback + return callback diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 6b0ae89f..3dce40c2 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -98,11 +98,6 @@ def swap(basic_swap, meta_swap, pool_type): # <--------------------- Metapool configuration ---------------------> -@pytest.fixture(scope="session") -def base_pool_deployer(): - return boa.load_partial("contracts/mocks/CurvePool.vy") - - @pytest.fixture() def base_pool(deployer, owner, alice, base_pool_tokens, base_pool_lp_token, base_pool_deployer): with boa.env.prank(deployer): diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index b5754176..d6cc8f1e 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -3,30 +3,27 @@ @pytest.fixture() -def plain_tokens(deployer, decimals): - tokens = [] +def plain_tokens(erc20_deployer, deployer, decimals): with boa.env.prank(deployer): - for i, d in enumerate(decimals): - tokens.append(boa.load("contracts/mocks/ERC20.vy", f"TKN{i}", f"TKN{i}", decimals[i])) - return tokens + return [erc20_deployer.deploy(f"TKN{i}", f"TKN{i}", decimals[i]) for i, d in enumerate(decimals)] @pytest.fixture() -def oracle_tokens(deployer, decimals): - tokens = [] +def oracle_tokens(erc20oracle_deployer, deployer, decimals): with boa.env.prank(deployer): - tokens.append(boa.load("contracts/mocks/ERC20Oracle.vy", "OTA", "OTA", 18, 1006470359024000000)) - tokens.append(boa.load("contracts/mocks/ERC20Oracle.vy", "OTB", "OTB", 18, 1007580460035000000)) - return tokens + return [ + erc20oracle_deployer.deploy("OTA", "OTA", 18, 1006470359024000000), + erc20oracle_deployer.deploy("OTB", "OTB", 18, 1007580460035000000), + ] @pytest.fixture() -def rebase_tokens(deployer, decimals): - tokens = [] +def rebase_tokens(erc20_rebasing_deployer, deployer, decimals): with boa.env.prank(deployer): - for i, d in enumerate(decimals): - tokens.append(boa.load("contracts/mocks/ERC20Rebasing.vy", f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], True)) - return tokens + return [ + erc20_rebasing_deployer.deploy(f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], True) + for i, d in enumerate(decimals) + ] @pytest.fixture() @@ -47,18 +44,15 @@ def base_pool_decimals(): @pytest.fixture() -def base_pool_tokens(deployer, base_pool_decimals): +def base_pool_tokens(erc20_deployer, deployer, base_pool_decimals): with boa.env.prank(deployer): - return [ - boa.load("contracts/mocks/ERC20.vy", c, c, base_pool_decimals[i]) - for i, c in enumerate(("DAI", "USDC", "USDT")) - ] + return [erc20_deployer.deploy(c, c, base_pool_decimals[i]) for i, c in enumerate(("DAI", "USDC", "USDT"))] @pytest.fixture() -def base_pool_lp_token(deployer): +def base_pool_lp_token(deployer, curve_token_v3_deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/CurveTokenV3.vy", "LP", "LP") + return curve_token_v3_deployer.deploy("LP", "LP") @pytest.fixture() @@ -68,28 +62,24 @@ def underlying_tokens(metapool_token, base_pool_tokens, base_pool_lp_token): # <--------------------- Gauge rewards ---------------------> @pytest.fixture() -def coin_reward(owner): +def coin_reward(owner, erc20_deployer): with boa.env.prank(owner): - return boa.load("contracts/mocks/ERC20.vy", "CR", "CR", 18) + return erc20_deployer.deploy("CR", "CR", 18) @pytest.fixture() -def coin_reward_a(owner, mint_owner): +def coin_reward_a(owner, mint_owner, erc20_deployer): with boa.env.prank(owner): - return boa.load("contracts/mocks/ERC20.vy", "CRa", "CRa", 18) + return erc20_deployer.deploy("CRa", "CRa", 18) @pytest.fixture() -def coin_reward_b(owner): +def coin_reward_b(owner, erc20_deployer): with boa.env.prank(owner): - return boa.load("contracts/mocks/ERC20.vy", "CRb", "CRb", 18) + return erc20_deployer.deploy("CRb", "CRb", 18) @pytest.fixture() -def coin_rewards_additional(owner): - coins = [] +def coin_rewards_additional(owner, erc20_deployer): with boa.env.prank(owner): - for i in range(8): - coins.append(boa.load("contracts/mocks/ERC20.vy", f"CR{i}", f"CR{i}", 18)) - - return coins + return [erc20_deployer.deploy(f"CR{i}", f"CR{i}", 18) for i in range(8)] diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index c4b254c2..242cbb46 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -13,21 +13,17 @@ def ng_base_pool_decimals(): return [18] * BASE_N_COINS -@pytest.fixture(scope="module") -def ng_base_pool_tokens(ng_base_pool_decimals): - tokens = [] - for i in range(BASE_N_COINS): - tokens.append(boa.load("contracts/mocks/ERC20.vy", f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i])) - - return tokens +@pytest.fixture() +def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer): + return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(BASE_N_COINS)] -@pytest.fixture(scope="module") -def meta_token(): - return boa.load("contracts/mocks/ERC20.vy", "OTA", "OTA", 18) +@pytest.fixture() +def meta_token(erc20_deployer): + return erc20_deployer.deploy("OTA", "OTA", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def ng_base_pool(deployer, factory, ng_base_pool_tokens, zero_address, amm_deployer, set_pool_implementations): pool_size = len(ng_base_pool_tokens) offpeg_fee_multiplier = 20000000000 diff --git a/tests/pools/meta/test_meta_zap.py b/tests/pools/meta/test_meta_zap.py index 2cffa6c4..4d72e194 100644 --- a/tests/pools/meta/test_meta_zap.py +++ b/tests/pools/meta/test_meta_zap.py @@ -9,9 +9,9 @@ @pytest.fixture(scope="module") -def meta_token(deployer): +def meta_token(deployer, erc20_deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20.vy", "OTA", "OTA", 18) + return erc20_deployer.deploy("OTA", "OTA", 18) @pytest.fixture(scope="module") @@ -63,10 +63,8 @@ def empty_swap( @pytest.fixture(scope="module") -def zap(base_pool, base_pool_tokens, base_pool_lp_token): - return boa.load( - "contracts/mocks/Zap.vy", base_pool.address, base_pool_lp_token.address, [a.address for a in base_pool_tokens] - ) +def zap(base_pool, base_pool_tokens, base_pool_lp_token, zap_deployer): + return zap_deployer.deploy(base_pool.address, base_pool_lp_token.address, [a.address for a in base_pool_tokens]) @pytest.fixture(scope="module") diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/test_erc4626_swaps.py index f686e4b8..08118590 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/test_erc4626_swaps.py @@ -37,32 +37,12 @@ def mint_tokens(charlie, pool_erc20_tokens, pool_tokens, swap, i): return amount_in -@pytest.fixture(scope="module") -def erc20_deployer(): - return boa.load_partial("contracts/mocks/ERC20.vy") - - @pytest.fixture() def asset(deployer, erc20_deployer): with boa.env.prank(deployer): return erc20_deployer.deploy("Asset", "AST", 8) # 8 decimals -@pytest.fixture(scope="module") -def erc4626_deployer(): - return boa.load_partial("contracts/mocks/ERC4626.vy") - - -@pytest.fixture(scope="module") -def erc20oracle_deployer(): - return boa.load_partial("contracts/mocks/ERC20Oracle.vy") - - -@pytest.fixture(scope="module") -def erc20rebasing_conditional_deployer(): - return boa.load_partial("contracts/mocks/ERC20RebasingConditional.vy") - - @pytest.fixture() def token_a(deployer, asset, erc4626_deployer): with boa.env.prank(deployer): @@ -91,7 +71,7 @@ def pool_erc20_tokens(asset, token_b, token_c): return [asset, token_b, token_c] -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def asset_types(pool_tokens): _asset_types = [] for token in pool_tokens: diff --git a/tests/pools/test_specific_liquidity_operations.py b/tests/pools/test_specific_liquidity_operations.py index fb127c58..6855d139 100644 --- a/tests/pools/test_specific_liquidity_operations.py +++ b/tests/pools/test_specific_liquidity_operations.py @@ -6,21 +6,21 @@ @pytest.fixture(scope="module") -def token_a(deployer): +def token_a(deployer, erc20oracle_deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20Oracle.vy", "OTA", "OTA", 18, 1006470359024000000) + return erc20oracle_deployer.deploy("OTA", "OTA", 18, 1006470359024000000) @pytest.fixture(scope="module") -def token_b(deployer): +def token_b(deployer, erc20oracle_deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20Oracle.vy", "OTB", "OTB", 18, 1000000000000000000) + return erc20oracle_deployer.deploy("OTB", "OTB", 18, 1000000000000000000) @pytest.fixture(scope="module") -def token_c(deployer): +def token_c(deployer, erc20_deployer): with boa.env.prank(deployer): - return boa.load("contracts/mocks/ERC20.vy", "OTC", "OTC", 18) + return erc20_deployer.deploy("OTC", "OTC", 18) @pytest.fixture(scope="module") From 3c72fbbb2b6ae91e888f948763f31fe3e2a6d706 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 16 Jan 2024 14:33:08 +0100 Subject: [PATCH 290/337] Less test cases --- tests/conftest.py | 12 ++-- tests/constants.py | 2 +- tests/factory/test_factory_meta.py | 8 +-- tests/factory/test_factory_plain.py | 7 +-- tests/fixtures/accounts.py | 91 +++++++++++++++-------------- 5 files changed, 62 insertions(+), 58 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0323a59d..3fce4ff5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ -import itertools import os +from itertools import combinations_with_replacement +from random import sample import boa import pytest @@ -33,11 +34,14 @@ def pytest_generate_tests(metafunc): items = [ (k, v) for k, v in TOKEN_TYPES.items() if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") ] - combinations = sorted(itertools.combinations_with_replacement(items, 2)) + # make all combinations possible + all_combinations = list(combinations_with_replacement(items, 2)) + # take 2 combinations for smaller test set + samples = sorted(sample(all_combinations, k=2)) metafunc.parametrize( "pool_token_types", - [(v1, v2) for (k1, v1), (k2, v2) in combinations], - ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in combinations], + [(v1, v2) for (k1, v1), (k2, v2) in samples], + ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in samples], ) if "metapool_token_type" in metafunc.fixturenames: diff --git a/tests/constants.py b/tests/constants.py index 06718302..073cdd09 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -1,3 +1,3 @@ POOL_TYPES = {"basic": 0, "meta": 1} TOKEN_TYPES = {"plain": 0, "oracle": 1, "rebasing": 2} -DECIMAL_PAIRS = [(18, 18), (10, 12)] +DECIMAL_PAIRS = [(18, 18)] # TODO: Do we need more pairs? diff --git a/tests/factory/test_factory_meta.py b/tests/factory/test_factory_meta.py index 0820f375..1243e964 100644 --- a/tests/factory/test_factory_meta.py +++ b/tests/factory/test_factory_meta.py @@ -29,20 +29,20 @@ def test_get_underlying_coins(factory, meta_swap, underlying_tokens): assert factory.get_underlying_coins(meta_swap.address) == [t.address for t in tokens] -def test_get_underlying_decimals(factory, meta_swap, base_pool_decimals, pool_type): +def test_get_underlying_decimals(factory, meta_swap, base_pool_decimals): assert factory.get_underlying_decimals(meta_swap.address) == [18] + base_pool_decimals -def test_get_metapool_rates(factory, meta_swap, base_pool, initial_setup): +def test_get_metapool_rates(meta_setup, factory, meta_swap, base_pool, base_pool_lp_token): assert factory.get_metapool_rates(meta_swap.address) == [10**18, base_pool.get_virtual_price()] -def test_get_underlying_balances(factory, meta_swap, base_pool, initial_setup): +def test_get_underlying_balances(meta_setup, factory, meta_swap, base_pool): assert factory.get_metapool_rates(meta_swap.address) == [10**18, base_pool.get_virtual_price()] @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(1, 4), 2)) -def test_find_pool_underlying_base_pool_only(self, factory, underlying_tokens, sending, receiving, zero_address): +def test_find_pool_underlying_base_pool_only(factory, underlying_tokens, sending, receiving, zero_address): assert factory.find_pool_for_coins(underlying_tokens[sending], underlying_tokens[receiving]) == zero_address diff --git a/tests/factory/test_factory_plain.py b/tests/factory/test_factory_plain.py index f4056ce1..be4bb690 100644 --- a/tests/factory/test_factory_plain.py +++ b/tests/factory/test_factory_plain.py @@ -3,11 +3,8 @@ @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_find_pool_for_coins(factory, basic_swap, plain_tokens, sending, receiving): - assert ( - factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) - == basic_swap.address - ) +def test_find_pool_for_coins(factory, swap, plain_tokens, sending, receiving): + assert factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) == swap.address def test_get_n_coins(factory, swap, plain_tokens, pool_size): diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 8e5bba2b..e9f679e0 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -196,58 +196,61 @@ def approve_meta_bob(bob, underlying_tokens, swap): @pytest.fixture() -def initial_setup( +def basic_setup(alice, bob, mint_alice, deposit_amounts, basic_swap, initial_balance, initial_amounts, pool_tokens): + mint_for_testing(bob, 1 * 10**18, None, True) + + with boa.env.prank(alice): + basic_swap.add_liquidity(deposit_amounts, 0) + + mint_account(bob, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(bob): + for token in pool_tokens: + token.approve(basic_swap.address, 2**256 - 1) + + +@pytest.fixture() +def meta_setup( alice, bob, - approve_alice, mint_alice, + approve_alice, deposit_amounts, - swap, - pool_type, + meta_swap, base_pool, base_pool_tokens, base_pool_decimals, base_pool_lp_token, - initial_balance, initial_amounts, - pool_tokens, underlying_tokens, ): - with boa.env.anchor(): - mint_for_testing(bob, 1 * 10**18, None, True) - - if pool_type == 0: - with boa.env.prank(alice): - swap.add_liquidity(deposit_amounts, 0) - - mint_account(bob, pool_tokens, initial_balance, initial_amounts) - with boa.env.prank(bob): - for token in pool_tokens: - token.approve(swap.address, 2**256 - 1) - - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - alice_bp_balance_norm = base_pool_lp_token.balanceOf(alice) / 10**18 - alice_mp_balance_norm = underlying_tokens[0].balanceOf(alice) / 10 ** underlying_tokens[0].decimals() - - if alice_mp_balance_norm < alice_bp_balance_norm: - mint_for_testing( - alice, - int(math.ceil(alice_bp_balance_norm) * 10 ** underlying_tokens[0].decimals()), - underlying_tokens[0], - ) - - with boa.env.prank(alice): - underlying_tokens[0].approve(swap.address, 2**256 - 1) - base_pool_lp_token.approve(swap.address, 2**256 - 1) - swap.add_liquidity(deposit_amounts, 0) - - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) - assert underlying_tokens[0].balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) - - with boa.env.prank(bob): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) - - yield + mint_for_testing(bob, 1 * 10**18, None, True) + + underlying_token = underlying_tokens[0] + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + alice_bp_balance_norm = base_pool_lp_token.balanceOf(alice) / 10**18 + alice_mp_balance_norm = underlying_token.balanceOf(alice) / 10 ** underlying_token.decimals() + + if alice_mp_balance_norm < alice_bp_balance_norm: + mint_for_testing( + alice, int(math.ceil(alice_bp_balance_norm) * 10 ** underlying_token.decimals()), underlying_token + ) + + with boa.env.prank(alice): + underlying_token.approve(meta_swap.address, 2**256 - 1) + base_pool_lp_token.approve(meta_swap.address, 2**256 - 1) + meta_swap.add_liquidity(deposit_amounts, 0) + + add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(bob, initial_amounts[0], underlying_token, False) + assert underlying_token.balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) + + with boa.env.prank(bob): + for underlying_token in underlying_tokens: + underlying_token.approve(meta_swap.address, 2**256 - 1) + + +@pytest.fixture() +def initial_setup(meta_setup, basic_setup, pool_type): + if pool_type == 0: + return basic_setup + return meta_setup From eb344cdbf0573ffda9995cea080373b76a740f5d Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 16 Jan 2024 14:58:57 +0100 Subject: [PATCH 291/337] Determinism --- tests/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3fce4ff5..450c6e9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import os from itertools import combinations_with_replacement -from random import sample +from random import sample, seed import boa import pytest @@ -34,10 +34,10 @@ def pytest_generate_tests(metafunc): items = [ (k, v) for k, v in TOKEN_TYPES.items() if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") ] - # make all combinations possible - all_combinations = list(combinations_with_replacement(items, 2)) - # take 2 combinations for smaller test set - samples = sorted(sample(all_combinations, k=2)) + + all_combinations = list(combinations_with_replacement(items, 2)) # make all combinations possible + seed(len(metafunc.fixturenames)) # make sure we get the same result in each worker + samples = sorted(sample(all_combinations, k=2)) # take 2 combinations for smaller test set metafunc.parametrize( "pool_token_types", [(v1, v2) for (k1, v1), (k2, v2) in samples], From c5712e097cf6dc241e27aacddc47cc56aa79376a Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 17 Jan 2024 12:42:59 +0100 Subject: [PATCH 292/337] Fix difference basic vs plain --- tests/conftest.py | 32 +++++++---- tests/factory/test_factory_basic.py | 60 ++++++++++++++++++++ tests/factory/test_factory_plain.py | 58 ------------------- tests/pools/meta/test_exchange_underlying.py | 2 +- 4 files changed, 83 insertions(+), 69 deletions(-) create mode 100644 tests/factory/test_factory_basic.py delete mode 100644 tests/factory/test_factory_plain.py diff --git a/tests/conftest.py b/tests/conftest.py index 450c6e9a..26d71998 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import os from itertools import combinations_with_replacement -from random import sample, seed +from random import Random import boa import pytest @@ -31,17 +31,11 @@ def pytest_generate_tests(metafunc): ) if "pool_token_types" in metafunc.fixturenames: - items = [ - (k, v) for k, v in TOKEN_TYPES.items() if not metafunc.definition.get_closest_marker(f"skip_{k}_tokens") - ] - - all_combinations = list(combinations_with_replacement(items, 2)) # make all combinations possible - seed(len(metafunc.fixturenames)) # make sure we get the same result in each worker - samples = sorted(sample(all_combinations, k=2)) # take 2 combinations for smaller test set + pool_token_pairs = get_pool_token_pairs(metafunc) metafunc.parametrize( "pool_token_types", - [(v1, v2) for (k1, v1), (k2, v2) in samples], - ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in samples], + [(v1, v2) for (k1, v1), (k2, v2) in pool_token_pairs], + ids=[f"(PoolTokenTypes={k1}+{k2})" for (k1, v1), (k2, v2) in pool_token_pairs], ) if "metapool_token_type" in metafunc.fixturenames: @@ -58,6 +52,24 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("initial_decimals", DECIMAL_PAIRS, ids=[f"(Decimals={i},{j})" for i, j in DECIMAL_PAIRS]) +def get_pool_token_pairs(metafunc): + for name, number in TOKEN_TYPES.items(): + if metafunc.definition.get_closest_marker(f"only_{name}_tokens"): + return [((name, number), (name, number))] + + items = [ + (name, number) + for name, number in TOKEN_TYPES.items() + if not metafunc.definition.get_closest_marker(f"skip_{name}_tokens") + ] + # make all combinations possible + all_combinations = list(combinations_with_replacement(items, 2)) + # make sure we get the same result in each worker + random = Random(len(metafunc.fixturenames)) + # take 2 combinations for smaller test set + return sorted(random.sample(all_combinations, k=2)) + + @pytest.fixture(scope="session") def pool_size(): return 2 diff --git a/tests/factory/test_factory_basic.py b/tests/factory/test_factory_basic.py new file mode 100644 index 00000000..c5a24049 --- /dev/null +++ b/tests/factory/test_factory_basic.py @@ -0,0 +1,60 @@ +import boa +import pytest + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_find_pool_for_coins(factory, basic_swap, pool_tokens, sending, receiving): + assert ( + factory.find_pool_for_coins(pool_tokens[sending].address, pool_tokens[receiving].address) == basic_swap.address + ) + + +def test_get_n_coins(factory, basic_swap, pool_tokens, pool_size): + assert factory.get_n_coins(basic_swap.address) == 2 + + +def test_get_coins(factory, basic_swap, pool_tokens, pool_size): + assert factory.get_coins(basic_swap.address) == [pt.address for pt in pool_tokens] + + +def test_get_decimals(factory, basic_swap, decimals): + assert factory.get_decimals(basic_swap.address) == decimals + + +def test_get_balances(factory, basic_swap, pool_size): + assert factory.get_balances(basic_swap.address) == [basic_swap.balances(i) for i in range(pool_size)] + + +def test_get_underlying_balances(factory, basic_swap): + with boa.reverts() as e: + factory.get_underlying_balances(basic_swap.address) + assert str(e) == "dev: pool is not a metapool" + + +def test_get_A(factory, basic_swap): + assert factory.get_A(basic_swap.address) == basic_swap.A() + + +def test_get_fees(factory, basic_swap): + assert factory.get_fees(basic_swap.address) == (basic_swap.fee(), basic_swap.admin_fee()) + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_get_coin_indices(factory, basic_swap, sending, receiving, pool_tokens): + i, j, is_underlying = factory.get_coin_indices( + basic_swap.address, pool_tokens[sending].address, pool_tokens[receiving].address + ) + assert i == sending + assert j == receiving + + +def test_get_implementation_address(factory, basic_swap, amm_implementation): + assert factory.get_implementation_address(basic_swap.address) == amm_implementation.address + + +def test_is_meta(factory, basic_swap): + assert factory.is_meta(basic_swap.address) is False + + +def test_get_pool_types(factory, basic_swap, pool_token_types): + assert factory.get_pool_asset_types(basic_swap.address) == list(pool_token_types) diff --git a/tests/factory/test_factory_plain.py b/tests/factory/test_factory_plain.py deleted file mode 100644 index be4bb690..00000000 --- a/tests/factory/test_factory_plain.py +++ /dev/null @@ -1,58 +0,0 @@ -import boa -import pytest - - -@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_find_pool_for_coins(factory, swap, plain_tokens, sending, receiving): - assert factory.find_pool_for_coins(plain_tokens[sending].address, plain_tokens[receiving].address) == swap.address - - -def test_get_n_coins(factory, swap, plain_tokens, pool_size): - assert factory.get_n_coins(swap.address) == 2 - - -def test_get_coins(factory, swap, plain_tokens, pool_size): - assert factory.get_coins(swap.address) == [pt.address for pt in plain_tokens] - - -def test_get_decimals(factory, swap, decimals): - assert factory.get_decimals(swap.address) == decimals - - -def test_get_balances(factory, swap, pool_size): - assert factory.get_balances(swap.address) == [swap.balances(i) for i in range(pool_size)] - - -def test_get_underlying_balances(factory, basic_swap): - with boa.reverts() as e: - factory.get_underlying_balances(basic_swap.address) - assert str(e) == "dev: pool is not a metapool" - - -def test_get_A(factory, swap): - assert factory.get_A(swap.address) == swap.A() - - -def test_get_fees(factory, swap): - assert factory.get_fees(swap.address) == (swap.fee(), swap.admin_fee()) - - -@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_get_coin_indices(factory, swap, sending, receiving, plain_tokens): - i, j, is_underlying = factory.get_coin_indices( - swap.address, plain_tokens[sending].address, plain_tokens[receiving].address - ) - assert i == sending - assert j == receiving - - -def test_get_implementation_address(factory, swap, amm_implementation): - assert factory.get_implementation_address(swap.address) == amm_implementation.address - - -def test_is_meta(factory, swap): - assert factory.is_meta(swap.address) is False - - -def test_get_pool_types(factory, swap, pool_token_types): - assert factory.get_pool_asset_types(swap.address) == list(pool_token_types) diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 2ed2c246..55943bf0 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -4,7 +4,7 @@ from tests.utils import approx -pytest.mark.usefixtures("initial_setup") +pytestmark = pytest.mark.usefixtures("initial_setup") @pytest.mark.skip_oracle_tokens From 56510d826141665f69dd41d12d1ac7475703e186 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 17 Jan 2024 15:03:37 +0100 Subject: [PATCH 293/337] Split pool tests --- .github/workflows/ci.yaml | 5 +- tests/pools/exchange/__init__.py | 0 tests/pools/exchange/test_exchange.py | 84 ++++++ .../{ => exchange}/test_exchange_received.py | 0 .../pools/exchange/test_exchange_receiver.py | 79 ++++++ tests/pools/exchange/test_exchange_reverts.py | 69 +++++ tests/pools/general/__init__.py | 0 .../{ => general}/test_donation_get_D.py | 0 .../pools/{ => general}/test_erc4626_swaps.py | 2 +- tests/pools/{ => general}/test_fees.py | 0 tests/pools/{ => general}/test_liquidity.py | 0 tests/pools/{ => general}/test_oracles.py | 7 +- tests/pools/{ => general}/test_ramp_A.py | 0 .../test_specific_liquidity_operations.py | 0 .../pools/{ => general}/test_swap_getters.py | 0 .../pools/{ => general}/test_virtual_price.py | 0 tests/pools/meta/test_exchange_underlying.py | 20 +- tests/pools/oracle/test_oracle.py | 55 ++-- tests/pools/test_exchange.py | 240 ------------------ 19 files changed, 275 insertions(+), 286 deletions(-) create mode 100644 tests/pools/exchange/__init__.py create mode 100644 tests/pools/exchange/test_exchange.py rename tests/pools/{ => exchange}/test_exchange_received.py (100%) create mode 100644 tests/pools/exchange/test_exchange_receiver.py create mode 100644 tests/pools/exchange/test_exchange_reverts.py create mode 100644 tests/pools/general/__init__.py rename tests/pools/{ => general}/test_donation_get_D.py (100%) rename tests/pools/{ => general}/test_erc4626_swaps.py (99%) rename tests/pools/{ => general}/test_fees.py (100%) rename tests/pools/{ => general}/test_liquidity.py (100%) rename tests/pools/{ => general}/test_oracles.py (96%) rename tests/pools/{ => general}/test_ramp_A.py (100%) rename tests/pools/{ => general}/test_specific_liquidity_operations.py (100%) rename tests/pools/{ => general}/test_swap_getters.py (100%) rename tests/pools/{ => general}/test_virtual_price.py (100%) delete mode 100644 tests/pools/test_exchange.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e5ac2182..19712253 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,7 +28,10 @@ jobs: matrix: name: - gauge - - pools + - pools/exchange + - pools/general + - pools/meta + - pools/oracle - factory steps: diff --git a/tests/pools/exchange/__init__.py b/tests/pools/exchange/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/exchange/test_exchange.py b/tests/pools/exchange/test_exchange.py new file mode 100644 index 00000000..99c21fac --- /dev/null +++ b/tests/pools/exchange/test_exchange.py @@ -0,0 +1,84 @@ +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_min_dy( + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_token_types, + metapool_token_type, + sending, + receiving, + decimals, +): + amount = 1000 * 10 ** decimals[sending] + initial_receiving = ( + pool_tokens[receiving].balanceOf(bob) if pool_type == 0 else underlying_tokens[receiving].balanceOf(bob) + ) + + min_dy = swap.get_dy(sending, receiving, amount) + # apply rebasing for expected dy + # Down rebasing breaks dy + if pool_type == 0 and pool_token_types[sending] == 2 and sending == 1: + min_dy -= pool_tokens[sending].balanceOf(swap.address) // 1000000 + + swap.exchange(sending, receiving, amount, min_dy - 1, sender=bob) + + if pool_type == 0: + received = pool_tokens[receiving].balanceOf(bob) + else: + received = underlying_tokens[receiving].balanceOf(bob) + + if (pool_type == 0 and 2 in pool_token_types) or (pool_type == 1 and metapool_token_type == 2): + assert abs(received - min_dy - initial_receiving) == pytest.approx(1, abs=received // 1000000) + else: + assert abs(received - min_dy - initial_receiving) <= 1 + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_min_dy_imbalanced( + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_token_types, + metapool_token_type, + sending, + receiving, + decimals, +): + amounts = [1_500_000 * 10**i for i in decimals] + scaler = amounts.copy() # used to scale token amounts when decimals are different + + amounts[sending] = 0 + amounts[receiving] = amounts[receiving] + + swap.add_liquidity(amounts, 0, sender=bob) + + # oracle + rate = 1 + if pool_type == 0: + if pool_token_types[sending] == 1: + rate = rate / (pool_tokens[sending].exchangeRate() / 10**18) + if pool_token_types[receiving] == 1: + rate = rate * (pool_tokens[receiving].exchangeRate() / 10**18) + + elif pool_type == 1: + if metapool_token_type == 1: + if sending == 0: + rate = rate / (underlying_tokens[0].exchangeRate() / 10**18) + + if receiving == 0: + rate = rate * (underlying_tokens[0].exchangeRate() / 10**18) + + # we need to scale these appropriately for tokens with different decimal values + min_dy_sending = swap.get_dy(sending, receiving, scaler[sending]) / scaler[receiving] + min_dy_receiving = swap.get_dy(receiving, sending, scaler[receiving]) / scaler[sending] + + assert min_dy_sending * rate > min_dy_receiving diff --git a/tests/pools/test_exchange_received.py b/tests/pools/exchange/test_exchange_received.py similarity index 100% rename from tests/pools/test_exchange_received.py rename to tests/pools/exchange/test_exchange_received.py diff --git a/tests/pools/exchange/test_exchange_receiver.py b/tests/pools/exchange/test_exchange_receiver.py new file mode 100644 index 00000000..ef03709b --- /dev/null +++ b/tests/pools/exchange/test_exchange_receiver.py @@ -0,0 +1,79 @@ +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +def test_add_liquidity(bob, charlie, swap, deposit_amounts): + swap.add_liquidity(deposit_amounts, 0, charlie, sender=bob) + + assert swap.balanceOf(bob) == 0 + assert swap.balanceOf(charlie) > 0 + + +def test_exchange( + bob, charlie, swap, pool_type, pool_tokens, underlying_tokens, decimals, pool_token_types, metapool_token_type +): + balance = pool_tokens[0].balanceOf(bob) if pool_type == 0 else underlying_tokens[0].balanceOf(bob) + + swap.exchange(1, 0, 1000 * 10**18, 0, charlie, sender=bob) + if pool_type == 0: + assert pool_tokens[0].balanceOf(charlie) > 0 + if pool_token_types[0] != 2: + assert pool_tokens[0].balanceOf(bob) == balance + else: + assert pool_tokens[0].balanceOf(bob) == pytest.approx(balance, rel=2e-2) + else: + assert underlying_tokens[0].balanceOf(charlie) > 0 + if metapool_token_type != 2: + assert underlying_tokens[0].balanceOf(bob) == balance + else: + assert underlying_tokens[0].balanceOf(bob) == pytest.approx(balance, rel=2e-2) + + +def test_remove_liquidity( + bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, pool_size, deposit_amounts +): + swap.add_liquidity(deposit_amounts, 0, sender=bob) + initial_amount = swap.balanceOf(bob) + withdraw_amount = initial_amount // 4 + swap.remove_liquidity(withdraw_amount, [0] * pool_size, charlie, sender=bob) + + i = 0 + if pool_type == 0: + for coin, amount in zip(pool_tokens, deposit_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx(deposit_amounts[0] * 2, rel=1.5e-2) + i += 1 + else: + for coin, amount in zip(underlying_tokens[:2], deposit_amounts): + assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx(deposit_amounts[0] * 2, rel=1.5e-2) + i += 1 + + assert swap.balanceOf(bob) == pytest.approx(deposit_amounts[0] * pool_size - withdraw_amount, rel=1.5e-2) + assert swap.totalSupply() == pytest.approx(deposit_amounts[0] * 2 * pool_size - withdraw_amount, rel=1.5e-2) + + +def test_remove_imbalanced( + bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, deposit_amounts +): + swap.add_liquidity(deposit_amounts, 0, sender=bob) + balance = swap.balanceOf(bob) + amounts = [i // 4 for i in initial_amounts] + swap.remove_liquidity_imbalance(amounts, balance, charlie, sender=bob) + + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) + + assert swap.balanceOf(bob) / balance == pytest.approx(0.5, rel=1.5e-2) + + +def test_remove_one_coin(alice, charlie, swap, pool_tokens, underlying_tokens): + swap.remove_liquidity_one_coin(10**18, 0, 0, charlie, sender=alice) + + assert swap.balanceOf(charlie) == 0 + assert swap.balanceOf(alice) > 0 diff --git a/tests/pools/exchange/test_exchange_reverts.py b/tests/pools/exchange/test_exchange_reverts.py new file mode 100644 index 00000000..86c7f739 --- /dev/null +++ b/tests/pools/exchange/test_exchange_reverts.py @@ -0,0 +1,69 @@ +import boa +import pytest + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_insufficient_balance(charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals): + amount = 10 ** decimals[sending] + + for token in pool_tokens + underlying_tokens: + assert token.balanceOf(charlie) == 0 + + # Charlie doesn't have any tokens, all balances are 0 + try: + swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) + assert False + except: # noqa: E722 + assert True + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +@pytest.mark.contains_rebasing_tokens +def test_zero_amount_swap(charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals): + with boa.reverts(): + swap.exchange(sending, receiving, 0, 0, sender=charlie) + + +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_min_dy_too_high(bob, swap, sending, receiving, decimals): + amount = 10 ** decimals[sending] + min_dy = swap.get_dy(sending, receiving, amount) + with boa.reverts(): + swap.exchange(sending, receiving, amount, min_dy + 2, sender=bob) + + +@pytest.mark.parametrize("idx", range(2)) +def test_same_coin(bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, idx, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [-1, -(2**127)]) +def test_i_below_zero(bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, 0, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [9, 2**127 - 1]) +def test_i_above_n_coins(bob, swap, idx): + with boa.reverts(): + swap.exchange(idx, 0, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [-1, -(2**127)]) +def test_j_below_zero(bob, swap, idx): + with boa.reverts(): + swap.exchange(0, idx, 0, 0, sender=bob) + + +@pytest.mark.parametrize("idx", [9, 2**127 - 1]) +def test_j_above_n_coins(bob, swap, idx): + with boa.reverts(): + swap.exchange(0, idx, 0, 0, sender=bob) + + +def test_nonpayable(swap, bob): + with boa.reverts(): + swap.exchange(0, 1, 0, 0, sender=bob, value=1) diff --git a/tests/pools/general/__init__.py b/tests/pools/general/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/test_donation_get_D.py b/tests/pools/general/test_donation_get_D.py similarity index 100% rename from tests/pools/test_donation_get_D.py rename to tests/pools/general/test_donation_get_D.py diff --git a/tests/pools/test_erc4626_swaps.py b/tests/pools/general/test_erc4626_swaps.py similarity index 99% rename from tests/pools/test_erc4626_swaps.py rename to tests/pools/general/test_erc4626_swaps.py index 08118590..d3e962e3 100644 --- a/tests/pools/test_erc4626_swaps.py +++ b/tests/pools/general/test_erc4626_swaps.py @@ -71,7 +71,7 @@ def pool_erc20_tokens(asset, token_b, token_c): return [asset, token_b, token_c] -@pytest.fixture(scope="session") +@pytest.fixture() def asset_types(pool_tokens): _asset_types = [] for token in pool_tokens: diff --git a/tests/pools/test_fees.py b/tests/pools/general/test_fees.py similarity index 100% rename from tests/pools/test_fees.py rename to tests/pools/general/test_fees.py diff --git a/tests/pools/test_liquidity.py b/tests/pools/general/test_liquidity.py similarity index 100% rename from tests/pools/test_liquidity.py rename to tests/pools/general/test_liquidity.py diff --git a/tests/pools/test_oracles.py b/tests/pools/general/test_oracles.py similarity index 96% rename from tests/pools/test_oracles.py rename to tests/pools/general/test_oracles.py index 28473f1b..8ed30321 100644 --- a/tests/pools/test_oracles.py +++ b/tests/pools/general/test_oracles.py @@ -4,7 +4,7 @@ import boa import pytest from boa.test import strategy -from hypothesis import given, settings +from hypothesis import HealthCheck, given, settings from tests.utils import approx from tests.utils.tokens import mint_for_testing @@ -132,7 +132,10 @@ def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amo check_oracle(swap, dt) -@given(amount=strategy("uint256", min_value=10**9, max_value=10**15)) +@given( + amount=strategy("uint256", min_value=10**9, max_value=10**15), + suppress_health_check=HealthCheck.function_scoped_fixture, +) @settings(**SETTINGS) def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimals, amount): # calc amount in: diff --git a/tests/pools/test_ramp_A.py b/tests/pools/general/test_ramp_A.py similarity index 100% rename from tests/pools/test_ramp_A.py rename to tests/pools/general/test_ramp_A.py diff --git a/tests/pools/test_specific_liquidity_operations.py b/tests/pools/general/test_specific_liquidity_operations.py similarity index 100% rename from tests/pools/test_specific_liquidity_operations.py rename to tests/pools/general/test_specific_liquidity_operations.py diff --git a/tests/pools/test_swap_getters.py b/tests/pools/general/test_swap_getters.py similarity index 100% rename from tests/pools/test_swap_getters.py rename to tests/pools/general/test_swap_getters.py diff --git a/tests/pools/test_virtual_price.py b/tests/pools/general/test_virtual_price.py similarity index 100% rename from tests/pools/test_virtual_price.py rename to tests/pools/general/test_virtual_price.py diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 55943bf0..10cb0f3b 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -7,30 +7,28 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -@pytest.mark.skip_oracle_tokens -@pytest.mark.skip_rebasing_tokens +@pytest.mark.only_plain_tokens @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) -def test_amounts(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): +def test_amounts(bob, meta_swap, metapool_token, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + metapool_token = [metapool_token[0], *metapool_token[2:]] amount_sent = 10 ** underlying_decimals[sending] - if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: - underlying_tokens[sending]._mint_for_testing(bob, amount_sent) + if sending > 0 and metapool_token[sending].balanceOf(bob) < amount_sent: + metapool_token[sending]._mint_for_testing(bob, amount_sent) expected_received = meta_swap.get_dy_underlying(sending, receiving, amount_sent) received_true = meta_swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 assert approx(received_true, expected_received, 1e-3) -@pytest.mark.skip_rebasing_tokens -@pytest.mark.skip_oracle_tokens +@pytest.mark.only_plain_tokens @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_min_dy_underlying(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): +def test_min_dy_underlying(bob, meta_swap, metapool_token, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals - underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] + metapool_token = [metapool_token[0], *metapool_token[2:]] amount = 10 ** underlying_decimals[sending] - underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) + metapool_token[sending]._mint_for_testing(bob, amount, sender=bob) expected = meta_swap.get_dy_underlying(sending, receiving, amount) received = meta_swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index b38ab1e4..09163210 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -8,39 +8,32 @@ DEPOSIT_AMOUNT = INITIAL_AMOUNT // 100 -@pytest.fixture(scope="module") -def initial_setup_alice( - alice, - deposit_amounts, - swap, - pool_type, - base_pool, - base_oracle_tokens, - base_pool_decimals, - base_pool_lp_token, - initial_balance, - initial_amounts, - oracle_tokens, - underlying_tokens, -): - with boa.env.anchor(): - mint_for_testing(alice, 1 * 10**18, None, True) +@pytest.fixture() +def initial_setup_alice(pool_type, basic_setup_alice, meta_setup_alice): + if pool_type == 0: + return basic_setup_alice + return meta_setup_alice - if pool_type == 0: - mint_account(alice, oracle_tokens, initial_balance, initial_amounts) - with boa.env.prank(alice): - for token in oracle_tokens: - token.approve(swap.address, 2**256 - 1) - else: - add_base_pool_liquidity(alice, base_pool, base_oracle_tokens, base_pool_decimals) - mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) +@pytest.fixture() +def basic_setup_alice(alice, initial_amounts, initial_balance, oracle_tokens, basic_swap): + mint_for_testing(alice, 1 * 10**18, None, True) + mint_account(alice, oracle_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in oracle_tokens: + token.approve(basic_swap.address, 2**256 - 1) - with boa.env.prank(alice): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) - yield +@pytest.fixture() +def meta_setup_alice( + alice, base_pool_tokens, base_pool, base_pool_decimals, initial_amounts, meta_swap, underlying_tokens +): + mint_for_testing(alice, 1 * 10**18, None, True) + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(meta_swap.address, 2**256 - 1) def test_initial_liquidity( @@ -79,8 +72,8 @@ def test_initial_liquidity( assert swap.admin_balances(1) == 0 -def test_oracles(alice, swap, pool_size, pool_type, pool_token_types, metapool_token_type): - assert swap._storage.oracles.get() != [0] * pool_size +def test_oracles(alice, swap, pool_size): + assert swap._immutables.rate_oracles.get() != [0] * pool_size def test_get_dy( diff --git a/tests/pools/test_exchange.py b/tests/pools/test_exchange.py deleted file mode 100644 index 2b0308f2..00000000 --- a/tests/pools/test_exchange.py +++ /dev/null @@ -1,240 +0,0 @@ -import boa -import pytest - -pytestmark = pytest.mark.usefixtures("initial_setup") - - -class TestExchange: - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_min_dy( - self, - bob, - swap, - pool_type, - pool_tokens, - underlying_tokens, - pool_token_types, - metapool_token_type, - sending, - receiving, - decimals, - ): - amount = 1000 * 10 ** decimals[sending] - initial_receiving = ( - pool_tokens[receiving].balanceOf(bob) if pool_type == 0 else underlying_tokens[receiving].balanceOf(bob) - ) - - min_dy = swap.get_dy(sending, receiving, amount) - # apply rebasing for expected dy - # Down rebasing breaks dy - if pool_type == 0 and pool_token_types[sending] == 2 and sending == 1: - min_dy -= pool_tokens[sending].balanceOf(swap.address) // 1000000 - - swap.exchange(sending, receiving, amount, min_dy - 1, sender=bob) - - if pool_type == 0: - received = pool_tokens[receiving].balanceOf(bob) - else: - received = underlying_tokens[receiving].balanceOf(bob) - - if (pool_type == 0 and 2 in pool_token_types) or (pool_type == 1 and metapool_token_type == 2): - assert abs(received - min_dy - initial_receiving) == pytest.approx(1, abs=received // 1000000) - else: - assert abs(received - min_dy - initial_receiving) <= 1 - - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_min_dy_imbalanced( - self, - bob, - swap, - pool_type, - pool_tokens, - underlying_tokens, - pool_token_types, - metapool_token_type, - sending, - receiving, - decimals, - ): - amounts = [1_500_000 * 10**i for i in decimals] - scaler = amounts.copy() # used to scale token amounts when decimals are different - - amounts[sending] = 0 - amounts[receiving] = amounts[receiving] - - swap.add_liquidity(amounts, 0, sender=bob) - - # oracle - rate = 1 - if pool_type == 0: - if pool_token_types[sending] == 1: - rate = rate / (pool_tokens[sending].exchangeRate() / 10**18) - if pool_token_types[receiving] == 1: - rate = rate * (pool_tokens[receiving].exchangeRate() / 10**18) - - elif pool_type == 1: - if metapool_token_type == 1: - if sending == 0: - rate = rate / (underlying_tokens[0].exchangeRate() / 10**18) - - if receiving == 0: - rate = rate * (underlying_tokens[0].exchangeRate() / 10**18) - - # we need to scale these appropriately for tokens with different decimal values - min_dy_sending = swap.get_dy(sending, receiving, scaler[sending]) / scaler[receiving] - min_dy_receiving = swap.get_dy(receiving, sending, scaler[receiving]) / scaler[sending] - - assert min_dy_sending * rate > min_dy_receiving - - class TestExchangeReverts: - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_insufficient_balance( - self, charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals - ): - amount = 10 ** decimals[sending] - - for token in pool_tokens + underlying_tokens: - assert token.balanceOf(charlie) == 0 - - # Charlie doesn't have any tokens, all balances are 0 - try: - swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) - assert False - except: # noqa: E722 - assert True - - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - @pytest.mark.contains_rebasing_tokens - def test_zero_amount_swap(self, charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals): - with boa.reverts(): - swap.exchange(sending, receiving, 0, 0, sender=charlie) - - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_min_dy_too_high(self, bob, swap, sending, receiving, decimals): - amount = 10 ** decimals[sending] - min_dy = swap.get_dy(sending, receiving, amount) - with boa.reverts(): - swap.exchange(sending, receiving, amount, min_dy + 2, sender=bob) - - @pytest.mark.parametrize("idx", range(2)) - def test_same_coin(self, bob, swap, idx): - with boa.reverts(): - swap.exchange(idx, idx, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [-1, -(2**127)]) - def test_i_below_zero(self, bob, swap, idx): - with boa.reverts(): - swap.exchange(idx, 0, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [9, 2**127 - 1]) - def test_i_above_n_coins(self, bob, swap, idx): - with boa.reverts(): - swap.exchange(idx, 0, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [-1, -(2**127)]) - def test_j_below_zero(self, bob, swap, idx): - with boa.reverts(): - swap.exchange(0, idx, 0, 0, sender=bob) - - @pytest.mark.parametrize("idx", [9, 2**127 - 1]) - def test_j_above_n_coins(self, bob, swap, idx): - with boa.reverts(): - swap.exchange(0, idx, 0, 0, sender=bob) - - def test_nonpayable(self, swap, bob): - with boa.reverts(): - swap.exchange(0, 1, 0, 0, sender=bob, value=1) - - class TestReceiver: - def test_add_liquidity(self, bob, charlie, swap, deposit_amounts): - swap.add_liquidity(deposit_amounts, 0, charlie, sender=bob) - - assert swap.balanceOf(bob) == 0 - assert swap.balanceOf(charlie) > 0 - - def test_exchange( - self, - bob, - charlie, - swap, - pool_type, - pool_tokens, - underlying_tokens, - decimals, - pool_token_types, - metapool_token_type, - ): - initial_balance = pool_tokens[0].balanceOf(bob) if pool_type == 0 else underlying_tokens[0].balanceOf(bob) - - swap.exchange(1, 0, 1000 * 10**18, 0, charlie, sender=bob) - if pool_type == 0: - assert pool_tokens[0].balanceOf(charlie) > 0 - if pool_token_types[0] != 2: - assert pool_tokens[0].balanceOf(bob) == initial_balance - else: - assert pool_tokens[0].balanceOf(bob) == pytest.approx(initial_balance, rel=2e-2) - else: - assert underlying_tokens[0].balanceOf(charlie) > 0 - if metapool_token_type != 2: - assert underlying_tokens[0].balanceOf(bob) == initial_balance - else: - assert underlying_tokens[0].balanceOf(bob) == pytest.approx(initial_balance, rel=2e-2) - - def test_remove_liquidity( - self, - bob, - swap, - charlie, - pool_type, - pool_tokens, - underlying_tokens, - initial_amounts, - pool_size, - deposit_amounts, - ): - swap.add_liquidity(deposit_amounts, 0, sender=bob) - initial_amount = swap.balanceOf(bob) - withdraw_amount = initial_amount // 4 - swap.remove_liquidity(withdraw_amount, [0] * pool_size, charlie, sender=bob) - - i = 0 - if pool_type == 0: - for coin, amount in zip(pool_tokens, deposit_amounts): - assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx( - deposit_amounts[0] * 2, rel=1.5e-2 - ) - i += 1 - else: - for coin, amount in zip(underlying_tokens[:2], deposit_amounts): - assert coin.balanceOf(swap) + coin.balanceOf(charlie) == pytest.approx( - deposit_amounts[0] * 2, rel=1.5e-2 - ) - i += 1 - - assert swap.balanceOf(bob) == pytest.approx(deposit_amounts[0] * pool_size - withdraw_amount, rel=1.5e-2) - assert swap.totalSupply() == pytest.approx(deposit_amounts[0] * 2 * pool_size - withdraw_amount, rel=1.5e-2) - - def test_remove_imbalanced( - self, bob, swap, charlie, pool_type, pool_tokens, underlying_tokens, initial_amounts, deposit_amounts - ): - swap.add_liquidity(deposit_amounts, 0, sender=bob) - initial_balance = swap.balanceOf(bob) - amounts = [i // 4 for i in initial_amounts] - swap.remove_liquidity_imbalance(amounts, initial_balance, charlie, sender=bob) - - if pool_type == 0: - for i, coin in enumerate(pool_tokens): - assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) - assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) - else: - for i, coin in enumerate(underlying_tokens[:2]): - assert coin.balanceOf(charlie) == pytest.approx(amounts[i], rel=1.5e-2) - assert coin.balanceOf(swap) == pytest.approx(initial_amounts[i] - amounts[i], rel=1.5e-2) - - assert swap.balanceOf(bob) / initial_balance == pytest.approx(0.5, rel=1.5e-2) - - def test_remove_one_coin(self, alice, charlie, swap, pool_type, pool_tokens, underlying_tokens): - swap.remove_liquidity_one_coin(10**18, 0, 0, charlie, sender=alice) - - assert swap.balanceOf(charlie) == 0 - assert swap.balanceOf(alice) > 0 From 032c5787bb4867c6091fe850505a3055a8dff631 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 19 Jan 2024 11:43:23 +0100 Subject: [PATCH 294/337] Split liquidity tests --- .github/workflows/ci.yaml | 3 +- tests/pools/general/test_liquidity.py | 415 ------------------ tests/pools/liquidity/__init__.py | 0 tests/pools/liquidity/test_add_liquidity.py | 150 +++++++ .../pools/liquidity/test_initial_liquidity.py | 71 +++ .../pools/liquidity/test_remove_liquidity.py | 54 +++ .../test_remove_liquidity_imbalance.py | 78 ++++ .../test_remove_liquidity_one_coin.py | 78 ++++ 8 files changed, 433 insertions(+), 416 deletions(-) delete mode 100644 tests/pools/general/test_liquidity.py create mode 100644 tests/pools/liquidity/__init__.py create mode 100644 tests/pools/liquidity/test_add_liquidity.py create mode 100644 tests/pools/liquidity/test_initial_liquidity.py create mode 100644 tests/pools/liquidity/test_remove_liquidity.py create mode 100644 tests/pools/liquidity/test_remove_liquidity_imbalance.py create mode 100644 tests/pools/liquidity/test_remove_liquidity_one_coin.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 19712253..bf16e43c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,6 +30,7 @@ jobs: - gauge - pools/exchange - pools/general + - pools/liquidity - pools/meta - pools/oracle - factory @@ -59,4 +60,4 @@ jobs: WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} run: | source .venv/bin/activate - pytest -n auto tests/${{ matrix.name }}/ + pytest --exitfirst --numprocesses=auto tests/${{ matrix.name }}/ diff --git a/tests/pools/general/test_liquidity.py b/tests/pools/general/test_liquidity.py deleted file mode 100644 index ba2aaf3b..00000000 --- a/tests/pools/general/test_liquidity.py +++ /dev/null @@ -1,415 +0,0 @@ -import boa -import pytest - -from tests.fixtures.accounts import add_base_pool_liquidity, mint_account -from tests.fixtures.constants import INITIAL_AMOUNT -from tests.utils.tokens import mint_for_testing -from tests.utils.transactions import call_returning_result_and_logs - - -class TestLiquidityMethods: - @pytest.mark.usefixtures("initial_setup") - class TestAddLiquidity: - def test_add_liquidity( - self, - bob, - swap, - pool_type, - pool_tokens, - underlying_tokens, - deposit_amounts, - initial_amounts, - pool_token_types, - metapool_token_type, - ): - swap.add_liquidity(deposit_amounts, 0, sender=bob) - is_ideal = True - - if pool_type == 0: - for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): - if pool_token_types[i] == 2: - is_ideal = False - assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 - else: - assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] - assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 - - if pool_token_types[i] == 1: - is_ideal = False - - ideal = len(pool_tokens) * INITIAL_AMOUNT // 2 * 10**18 - if is_ideal: - assert abs(swap.balanceOf(bob) - ideal) <= 1 - assert abs(swap.totalSupply() - ideal * 2) <= 2 - else: - if metapool_token_type == 2: - assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - deposit_amounts[0] - assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] * 2 - else: - assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - deposit_amounts[0] - assert underlying_tokens[0].balanceOf(swap.address) == deposit_amounts[0] * 2 - - if metapool_token_type == 0: - ideal = INITIAL_AMOUNT * 10**18 # // 2 * 2 - assert abs(swap.balanceOf(bob) - ideal) <= 1 - assert abs(swap.totalSupply() - ideal * 2) <= 2 - - assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - deposit_amounts[1] - assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] * 2 - - @pytest.mark.parametrize("idx", (0, 1)) - def test_add_one_coin( - self, - bob, - swap, - pool_type, - pool_tokens, - underlying_tokens, - deposit_amounts, - initial_amounts, - pool_token_types, - metapool_token_type, - idx, - ): - amounts = [0] * len(pool_tokens) - amounts[idx] = deposit_amounts[idx] - - swap.add_liquidity(amounts, 0, sender=bob) - is_ideal = True - - if pool_type == 0: - for i, pool_token in enumerate(pool_tokens): - if pool_token_types[i] == 2: - is_ideal = False - assert pool_token.balanceOf(bob) >= initial_amounts[i] - amounts[i] - 1 - assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] + amounts[i] - 1 - else: - assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] - assert pool_token.balanceOf(swap.address) == deposit_amounts[i] + amounts[i] - else: - if metapool_token_type == 2: - is_ideal = False - assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - amounts[0] - 1 - assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] + amounts[0] - 1 - else: - assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - amounts[0] - assert underlying_tokens[0].balanceOf(swap) == deposit_amounts[0] + amounts[0] - - assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - amounts[1] - assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] + amounts[1] - - difference = abs(swap.balanceOf(bob) - deposit_amounts[idx]) - if is_ideal: - assert difference / (deposit_amounts[idx]) < 0.01 - else: - assert difference / (deposit_amounts[idx]) < 0.02 - - def test_insufficient_balance(self, charlie, swap, pool_type, decimals, meta_decimals): - if pool_type == 0: - amounts = [(10**i) for i in decimals] - else: - amounts = [(10**i) for i in [meta_decimals, 18]] - - with boa.reverts(): # invalid approval or balance - swap.add_liquidity(amounts, 0, sender=charlie) - - def test_min_amount_too_high(self, bob, swap, pool_type, deposit_amounts, pool_tokens): - size = 2 - if pool_type == 0: - size = len(pool_tokens) - - with boa.reverts(): - swap.add_liquidity(deposit_amounts, size * INITIAL_AMOUNT // 2 * 10**18 * 101 // 100, sender=bob) - - def test_event(self, bob, swap, pool_type, deposit_amounts, pool_tokens, pool_token_types, metapool_token_type): - size = 2 - check_invariant = True - if pool_type == 0: - size = len(pool_tokens) - - for t in pool_token_types: - if t != 0: - check_invariant = False - - if pool_type == 1: - if metapool_token_type != 0: - check_invariant = False - - _, events = call_returning_result_and_logs(swap, "add_liquidity", deposit_amounts, 0, sender=bob) - - assert len(events) == 4 # Transfer token1, Transfer token2, Transfer LP, Add liquidity - if check_invariant: - assert ( - repr(events[3]) == f"AddLiquidity(provider={bob}, token_amounts={deposit_amounts}, fees=[0, 0], " - f"invariant={size * INITIAL_AMOUNT * 10**18}, token_supply={swap.totalSupply()})" - ) - - def test_send_eth(self, bob, swap, deposit_amounts): - with boa.reverts(): - swap.add_liquidity(deposit_amounts, 0, sender=bob, value=1) - - class TestInitialLiquidity: - @pytest.fixture(scope="module") - def initial_setup_alice( - self, - alice, - deposit_amounts, - swap, - pool_type, - base_pool, - base_pool_tokens, - base_pool_decimals, - base_pool_lp_token, - initial_balance, - initial_amounts, - pool_tokens, - underlying_tokens, - ): - with boa.env.anchor(): - mint_for_testing(alice, 1 * 10**18, None, True) - - if pool_type == 0: - mint_account(alice, pool_tokens, initial_balance, initial_amounts) - with boa.env.prank(alice): - for token in pool_tokens: - token.approve(swap.address, 2**256 - 1) - - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) - - with boa.env.prank(alice): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) - - yield - - @pytest.mark.parametrize("min_amount", [0, 10**18]) - def test_initial( - self, - alice, - initial_setup_alice, - swap, - pool_type, - pool_tokens, - underlying_tokens, - pool_token_types, - metapool_token_type, - min_amount, - decimals, - meta_decimals, - deposit_amounts, - initial_amounts, - ): - swap.add_liquidity(deposit_amounts, len(pool_tokens) * min_amount, sender=alice) - - token_types = pool_token_types if pool_type == 0 else [metapool_token_type, 18] - - for coin, und_coin, amount, initial, pool_token_type in zip( - pool_tokens, underlying_tokens, deposit_amounts, initial_amounts, token_types - ): - if pool_type == 0: - assert coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) - assert coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) - else: - assert und_coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) - assert und_coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) - - @pytest.mark.usefixtures("initial_setup") - class TestRemoveLiquidity: - @pytest.mark.parametrize("min_amount", (0, 1)) - def test_remove_liquidity( - self, alice, swap, pool_type, pool_tokens, underlying_tokens, min_amount, deposit_amounts - ): - swap.remove_liquidity(swap.balanceOf(alice), [i * min_amount for i in deposit_amounts], sender=alice) - - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - - for coin, amount in zip(coins, deposit_amounts): - assert coin.balanceOf(alice) == pytest.approx(amount * 2, rel=1.5e-2) - assert coin.balanceOf(swap) == 0 - - assert swap.balanceOf(alice) == 0 - assert swap.totalSupply() == 0 - - def test_remove_partial(self, alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size): - initial_amount = swap.balanceOf(alice) - withdraw_amount = initial_amount // 2 - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) - - for coin in coins: - assert coin.balanceOf(swap) + coin.balanceOf(alice) == pytest.approx(initial_amount, rel=1.5e-2) - - assert swap.balanceOf(alice) == initial_amount - withdraw_amount - assert swap.totalSupply() == initial_amount - withdraw_amount - - @pytest.mark.parametrize("idx", range(2)) - def test_below_min_amount(self, alice, swap, initial_amounts, idx): - min_amount = initial_amounts.copy() - min_amount[idx] += 1 - - with boa.reverts(): - swap.remove_liquidity(swap.balanceOf(alice), min_amount, sender=alice) - - def test_amount_exceeds_balance(self, alice, swap, pool_size): - with boa.reverts(): - swap.remove_liquidity(swap.balanceOf(alice) + 1, [0] * pool_size, sender=alice) - - def test_event(self, alice, bob, swap, pool_type, pool_size): - swap.transfer(bob, 10**18, sender=alice) - _, events = call_returning_result_and_logs( - swap, "remove_liquidity", 10**18, [0] * pool_size, sender=alice - ) - - assert f"RemoveLiquidity(provider={alice}" in repr(events[3]) - - @pytest.mark.usefixtures("initial_setup") - class TestRemoveLiquidityImbalance: - @pytest.mark.parametrize("divisor", [2, 5, 10]) - def test_remove_balanced( - self, alice, swap, pool_type, pool_tokens, underlying_tokens, divisor, deposit_amounts, initial_amounts - ): - initial_balance = swap.balanceOf(alice) - amounts = [i // divisor for i in deposit_amounts] - swap.remove_liquidity_imbalance(amounts, initial_balance, sender=alice) - - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - - for i, coin in enumerate(coins): - assert coin.balanceOf(alice) == pytest.approx( - amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2 - ) - assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) - - assert swap.balanceOf(alice) / initial_balance == pytest.approx(1 - 1 / divisor, rel=1.5e-2) - - @pytest.mark.parametrize("idx", range(2)) - def test_remove_one( - self, - alice, - swap, - pool_type, - pool_tokens, - underlying_tokens, - pool_size, - idx, - deposit_amounts, - initial_amounts, - ): - amounts = [0] * pool_size - amounts[idx] = deposit_amounts[idx] // 2 - - lp_balance = pool_size * deposit_amounts[idx] - swap.remove_liquidity_imbalance(amounts, lp_balance, sender=alice) - - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - - for i, coin in enumerate(coins): - assert coin.balanceOf(alice) == pytest.approx( - amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2 - ) - assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) - - actual_balance = swap.balanceOf(alice) - actual_total_supply = swap.totalSupply() - ideal_balance = (2 * pool_size - 1) * lp_balance / (2 * pool_size) - - assert actual_balance == actual_total_supply - assert ideal_balance * 0.9994 < actual_balance - assert actual_balance < ideal_balance * 1.07 - - @pytest.mark.parametrize("divisor", [1, 2, 10]) - def test_exceed_max_burn(self, alice, swap, pool_size, divisor, deposit_amounts): - amounts = [i // divisor for i in deposit_amounts] - max_burn = pool_size * 1_000_000 * 10**18 // divisor - - with boa.reverts(): - swap.remove_liquidity_imbalance(amounts, max_burn - 1, sender=alice) - - def test_cannot_remove_zero(self, alice, swap, pool_size): - with boa.reverts(): - swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) - - def test_no_totalsupply(self, alice, swap, pool_size): - swap.remove_liquidity(swap.totalSupply(), [0] * pool_size, sender=alice) - with boa.reverts(): - swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) - - def test_event(self, alice, bob, swap, pool_type, pool_size, deposit_amounts): - swap.transfer(bob, swap.balanceOf(alice), sender=alice) - amounts = [i // 5 for i in deposit_amounts] - max_burn = pool_size * 1_000_000 * 10**18 - - _, events = call_returning_result_and_logs( - swap, "remove_liquidity_imbalance", amounts, max_burn, sender=bob - ) - - assert f"RemoveLiquidityImbalance(provider={bob}" in repr(events[3]) - - @pytest.mark.usefixtures("initial_setup") - class TestRemoveLiquidityOneCoin: - @pytest.mark.parametrize("idx", range(2)) - def test_amount_received(self, alice, swap, pool_type, pool_tokens, underlying_tokens, decimals, idx): - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - initial_amount = coins[idx].balanceOf(alice) - - swap.remove_liquidity_one_coin(10**18, idx, 0, sender=alice) - ideal = 10 ** decimals[idx] - assert ideal * 0.99 <= coins[idx].balanceOf(alice) - initial_amount <= ideal - - @pytest.mark.parametrize("idx", range(2)) - @pytest.mark.parametrize("divisor", [1, 5, 42]) - def test_lp_token_balance(self, alice, swap, idx, divisor): - initial_amount = swap.balanceOf(alice) - amount = initial_amount // divisor - - if divisor == 1: - with boa.reverts(): - swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) - else: - swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) - - assert swap.balanceOf(alice) + amount == initial_amount - - @pytest.mark.parametrize("idx", range(2)) - def test_expected_vs_actual(self, alice, swap, pool_type, pool_tokens, underlying_tokens, idx): - coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] - initial_amount = coins[idx].balanceOf(alice) - amount = swap.balanceOf(alice) // 10 - - expected = swap.calc_withdraw_one_coin(amount, idx) - swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) - assert coins[idx].balanceOf(alice) == expected + initial_amount - - @pytest.mark.parametrize("idx", range(2)) - def test_below_min_amount(self, alice, swap, idx): - amount = swap.balanceOf(alice) - - expected = swap.calc_withdraw_one_coin(amount, idx) - with boa.reverts(): - swap.remove_liquidity_one_coin(amount, idx, expected + 1, sender=alice) - - @pytest.mark.parametrize("idx", range(2)) - def test_amount_exceeds_balance(self, bob, swap, idx): - with boa.reverts(): - swap.remove_liquidity_one_coin(1, idx, 0, sender=bob) - - def test_below_zero(self, alice, swap): - with boa.reverts(): - swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) - - def test_above_n_coins(self, alice, swap, pool_size): - with boa.reverts(): - swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) - - @pytest.mark.parametrize("idx", range(2)) - def test_event(self, alice, bob, swap, idx, pool_type): - swap.transfer(bob, 10**18, sender=alice) - _, events = call_returning_result_and_logs(swap, "remove_liquidity_one_coin", 10**18, idx, 0, sender=bob) - - if pool_type == 0: - assert f"RemoveLiquidityOne(provider={bob}" in repr(events[2]) - else: - assert f"RemoveLiquidityOne(provider={bob}" in repr(events[3]) diff --git a/tests/pools/liquidity/__init__.py b/tests/pools/liquidity/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/pools/liquidity/test_add_liquidity.py b/tests/pools/liquidity/test_add_liquidity.py new file mode 100644 index 00000000..72a3ac28 --- /dev/null +++ b/tests/pools/liquidity/test_add_liquidity.py @@ -0,0 +1,150 @@ +import boa +import pytest + +from tests.fixtures.constants import INITIAL_AMOUNT +from tests.utils.transactions import call_returning_result_and_logs + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +def test_add_liquidity( + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + deposit_amounts, + initial_amounts, + pool_token_types, + metapool_token_type, +): + swap.add_liquidity(deposit_amounts, 0, sender=bob) + is_ideal = True + + if pool_type == 0: + for i, (pool_token, amount) in enumerate(zip(pool_tokens, deposit_amounts)): + if pool_token_types[i] == 2: + is_ideal = False + assert pool_token.balanceOf(bob) >= initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] * 2 + else: + assert pool_token.balanceOf(bob) == initial_amounts[i] - deposit_amounts[i] + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] * 2 + + if pool_token_types[i] == 1: + is_ideal = False + + ideal = len(pool_tokens) * INITIAL_AMOUNT // 2 * 10**18 + if is_ideal: + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 + else: + if metapool_token_type == 2: + assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - deposit_amounts[0] + assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] * 2 + else: + assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - deposit_amounts[0] + assert underlying_tokens[0].balanceOf(swap.address) == deposit_amounts[0] * 2 + + if metapool_token_type == 0: + ideal = INITIAL_AMOUNT * 10**18 # // 2 * 2 + assert abs(swap.balanceOf(bob) - ideal) <= 1 + assert abs(swap.totalSupply() - ideal * 2) <= 2 + + assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - deposit_amounts[1] + assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] * 2 + + +@pytest.mark.parametrize("idx", (0, 1)) +def test_add_one_coin( + bob, + swap, + pool_type, + pool_tokens, + underlying_tokens, + deposit_amounts, + initial_amounts, + pool_token_types, + metapool_token_type, + idx, +): + amounts = [0] * len(pool_tokens) + amounts[idx] = deposit_amounts[idx] + + swap.add_liquidity(amounts, 0, sender=bob) + is_ideal = True + + if pool_type == 0: + for i, pool_token in enumerate(pool_tokens): + if pool_token_types[i] == 2: + is_ideal = False + assert pool_token.balanceOf(bob) >= initial_amounts[i] - amounts[i] - 1 + assert pool_token.balanceOf(swap.address) >= deposit_amounts[i] + amounts[i] - 1 + else: + assert pool_token.balanceOf(bob) == initial_amounts[i] - amounts[i] + assert pool_token.balanceOf(swap.address) == deposit_amounts[i] + amounts[i] + else: + if metapool_token_type == 2: + is_ideal = False + assert underlying_tokens[0].balanceOf(bob) >= initial_amounts[0] - amounts[0] - 1 + assert underlying_tokens[0].balanceOf(swap.address) >= deposit_amounts[0] + amounts[0] - 1 + else: + assert underlying_tokens[0].balanceOf(bob) == initial_amounts[0] - amounts[0] + assert underlying_tokens[0].balanceOf(swap) == deposit_amounts[0] + amounts[0] + + assert underlying_tokens[1].balanceOf(bob) == initial_amounts[1] - amounts[1] + assert underlying_tokens[1].balanceOf(swap) == deposit_amounts[1] + amounts[1] + + difference = abs(swap.balanceOf(bob) - deposit_amounts[idx]) + if is_ideal: + assert difference / (deposit_amounts[idx]) < 0.01 + else: + assert difference / (deposit_amounts[idx]) < 0.02 + + +def test_insufficient_balance(charlie, swap, pool_type, decimals, meta_decimals): + if pool_type == 0: + amounts = [(10**i) for i in decimals] + else: + amounts = [(10**i) for i in [meta_decimals, 18]] + + with boa.reverts(): # invalid approval or balance + swap.add_liquidity(amounts, 0, sender=charlie) + + +def test_min_amount_too_high(bob, swap, pool_type, deposit_amounts, pool_tokens): + size = 2 + if pool_type == 0: + size = len(pool_tokens) + + with boa.reverts(): + swap.add_liquidity(deposit_amounts, size * INITIAL_AMOUNT // 2 * 10**18 * 101 // 100, sender=bob) + + +def test_event(bob, swap, pool_type, deposit_amounts, pool_tokens, pool_token_types, metapool_token_type): + size = 2 + check_invariant = True + if pool_type == 0: + size = len(pool_tokens) + + for t in pool_token_types: + if t != 0: + check_invariant = False + + if pool_type == 1: + if metapool_token_type != 0: + check_invariant = False + + _, events = call_returning_result_and_logs(swap, "add_liquidity", deposit_amounts, 0, sender=bob) + + assert len(events) == 4 # Transfer token1, Transfer token2, Transfer LP, Add liquidity + if check_invariant: + assert ( + repr(events[3]) == f"AddLiquidity(provider={bob}, token_amounts={deposit_amounts}, fees=[0, 0], " + f"invariant={size * INITIAL_AMOUNT * 10 ** 18}, token_supply={swap.totalSupply()})" + ) + + +def test_send_eth(bob, swap, deposit_amounts): + with boa.reverts(): + swap.add_liquidity(deposit_amounts, 0, sender=bob, value=1) diff --git a/tests/pools/liquidity/test_initial_liquidity.py b/tests/pools/liquidity/test_initial_liquidity.py new file mode 100644 index 00000000..f09e59ec --- /dev/null +++ b/tests/pools/liquidity/test_initial_liquidity.py @@ -0,0 +1,71 @@ +import boa +import pytest + +from tests.fixtures.accounts import add_base_pool_liquidity, mint_account +from tests.utils.tokens import mint_for_testing + + +@pytest.fixture(scope="module") +def initial_setup_alice( + alice, + deposit_amounts, + swap, + pool_type, + base_pool, + base_pool_tokens, + base_pool_decimals, + base_pool_lp_token, + initial_balance, + initial_amounts, + pool_tokens, + underlying_tokens, +): + with boa.env.anchor(): + mint_for_testing(alice, 1 * 10**18, None, True) + + if pool_type == 0: + mint_account(alice, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in pool_tokens: + token.approve(swap.address, 2**256 - 1) + + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) + + yield + + +@pytest.mark.parametrize("min_amount", [0, 10**18]) +def test_initial( + alice, + initial_setup_alice, + swap, + pool_type, + pool_tokens, + underlying_tokens, + pool_token_types, + metapool_token_type, + min_amount, + decimals, + meta_decimals, + deposit_amounts, + initial_amounts, +): + swap.add_liquidity(deposit_amounts, len(pool_tokens) * min_amount, sender=alice) + + token_types = pool_token_types if pool_type == 0 else [metapool_token_type, 18] + + for coin, und_coin, amount, initial, pool_token_type in zip( + pool_tokens, underlying_tokens, deposit_amounts, initial_amounts, token_types + ): + if pool_type == 0: + assert coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) + else: + assert und_coin.balanceOf(alice) == pytest.approx(initial - amount, rel=1.5e-2) + assert und_coin.balanceOf(swap) == pytest.approx(amount, rel=1.5e-2) diff --git a/tests/pools/liquidity/test_remove_liquidity.py b/tests/pools/liquidity/test_remove_liquidity.py new file mode 100644 index 00000000..4c67a70b --- /dev/null +++ b/tests/pools/liquidity/test_remove_liquidity.py @@ -0,0 +1,54 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("min_amount", (0, 1)) +def test_remove_liquidity(alice, swap, pool_type, pool_tokens, underlying_tokens, min_amount, deposit_amounts): + swap.remove_liquidity(swap.balanceOf(alice), [i * min_amount for i in deposit_amounts], sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for coin, amount in zip(coins, deposit_amounts): + assert coin.balanceOf(alice) == pytest.approx(amount * 2, rel=1.5e-2) + assert coin.balanceOf(swap) == 0 + + assert swap.balanceOf(alice) == 0 + assert swap.totalSupply() == 0 + + +def test_remove_partial(alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size): + initial_amount = swap.balanceOf(alice) + withdraw_amount = initial_amount // 2 + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + swap.remove_liquidity(withdraw_amount, [0] * pool_size, sender=alice) + + for coin in coins: + assert coin.balanceOf(swap) + coin.balanceOf(alice) == pytest.approx(initial_amount, rel=1.5e-2) + + assert swap.balanceOf(alice) == initial_amount - withdraw_amount + assert swap.totalSupply() == initial_amount - withdraw_amount + + +@pytest.mark.parametrize("idx", range(2)) +def test_below_min_amount(alice, swap, initial_amounts, idx): + min_amount = initial_amounts.copy() + min_amount[idx] += 1 + + with boa.reverts(): + swap.remove_liquidity(swap.balanceOf(alice), min_amount, sender=alice) + + +def test_amount_exceeds_balance(alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity(swap.balanceOf(alice) + 1, [0] * pool_size, sender=alice) + + +def test_event(alice, bob, swap, pool_size): + swap.transfer(bob, 10**18, sender=alice) + _, events = call_returning_result_and_logs(swap, "remove_liquidity", 10**18, [0] * pool_size, sender=alice) + + assert f"RemoveLiquidity(provider={alice}" in repr(events[3]) diff --git a/tests/pools/liquidity/test_remove_liquidity_imbalance.py b/tests/pools/liquidity/test_remove_liquidity_imbalance.py new file mode 100644 index 00000000..2cb5fad6 --- /dev/null +++ b/tests/pools/liquidity/test_remove_liquidity_imbalance.py @@ -0,0 +1,78 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("divisor", [2, 5, 10]) +def test_remove_balanced( + alice, swap, pool_type, pool_tokens, underlying_tokens, divisor, deposit_amounts, initial_amounts +): + initial_balance = swap.balanceOf(alice) + amounts = [i // divisor for i in deposit_amounts] + swap.remove_liquidity_imbalance(amounts, initial_balance, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for i, coin in enumerate(coins): + assert coin.balanceOf(alice) == pytest.approx(amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) + + assert swap.balanceOf(alice) / initial_balance == pytest.approx(1 - 1 / divisor, rel=1.5e-2) + + +@pytest.mark.parametrize("idx", range(2)) +def test_remove_one( + alice, swap, pool_type, pool_tokens, underlying_tokens, pool_size, idx, deposit_amounts, initial_amounts +): + amounts = [0] * pool_size + amounts[idx] = deposit_amounts[idx] // 2 + + lp_balance = pool_size * deposit_amounts[idx] + swap.remove_liquidity_imbalance(amounts, lp_balance, sender=alice) + + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + + for i, coin in enumerate(coins): + assert coin.balanceOf(alice) == pytest.approx(amounts[i] + initial_amounts[i] - deposit_amounts[i], rel=1.5e-2) + assert coin.balanceOf(swap) == pytest.approx(deposit_amounts[i] - amounts[i], rel=1.5e-2) + + actual_balance = swap.balanceOf(alice) + actual_total_supply = swap.totalSupply() + ideal_balance = (2 * pool_size - 1) * lp_balance / (2 * pool_size) + + assert actual_balance == actual_total_supply + assert ideal_balance * 0.9994 < actual_balance + assert actual_balance < ideal_balance * 1.07 + + +@pytest.mark.parametrize("divisor", [1, 2, 10]) +def test_exceed_max_burn(alice, swap, pool_size, divisor, deposit_amounts): + amounts = [i // divisor for i in deposit_amounts] + max_burn = pool_size * 1_000_000 * 10**18 // divisor + + with boa.reverts(): + swap.remove_liquidity_imbalance(amounts, max_burn - 1, sender=alice) + + +def test_cannot_remove_zero(alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) + + +def test_no_total_supply(alice, swap, pool_size): + swap.remove_liquidity(swap.totalSupply(), [0] * pool_size, sender=alice) + with boa.reverts(): + swap.remove_liquidity_imbalance([0] * pool_size, 0, sender=alice) + + +def test_event(alice, bob, swap, pool_size, deposit_amounts): + swap.transfer(bob, swap.balanceOf(alice), sender=alice) + amounts = [i // 5 for i in deposit_amounts] + max_burn = pool_size * 1_000_000 * 10**18 + + _, events = call_returning_result_and_logs(swap, "remove_liquidity_imbalance", amounts, max_burn, sender=bob) + + assert f"RemoveLiquidityImbalance(provider={bob}" in repr(events[3]) diff --git a/tests/pools/liquidity/test_remove_liquidity_one_coin.py b/tests/pools/liquidity/test_remove_liquidity_one_coin.py new file mode 100644 index 00000000..4864e0bd --- /dev/null +++ b/tests/pools/liquidity/test_remove_liquidity_one_coin.py @@ -0,0 +1,78 @@ +import boa +import pytest + +from tests.utils.transactions import call_returning_result_and_logs + +pytestmark = pytest.mark.usefixtures("initial_setup") + + +@pytest.mark.parametrize("idx", range(2)) +def test_amount_received(alice, swap, pool_type, pool_tokens, underlying_tokens, decimals, idx): + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + initial_amount = coins[idx].balanceOf(alice) + + swap.remove_liquidity_one_coin(10**18, idx, 0, sender=alice) + ideal = 10 ** decimals[idx] + assert ideal * 0.99 <= coins[idx].balanceOf(alice) - initial_amount <= ideal + + +@pytest.mark.parametrize("idx", range(2)) +@pytest.mark.parametrize("divisor", [1, 5, 42]) +def test_lp_token_balance(alice, swap, idx, divisor): + initial_amount = swap.balanceOf(alice) + amount = initial_amount // divisor + + if divisor == 1: + with boa.reverts(): + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + else: + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + + assert swap.balanceOf(alice) + amount == initial_amount + + +@pytest.mark.parametrize("idx", range(2)) +def test_expected_vs_actual(alice, swap, pool_type, pool_tokens, underlying_tokens, idx): + coins = pool_tokens if pool_type == 0 else underlying_tokens[:2] + initial_amount = coins[idx].balanceOf(alice) + amount = swap.balanceOf(alice) // 10 + + expected = swap.calc_withdraw_one_coin(amount, idx) + swap.remove_liquidity_one_coin(amount, idx, 0, sender=alice) + assert coins[idx].balanceOf(alice) == expected + initial_amount + + +@pytest.mark.parametrize("idx", range(2)) +def test_below_min_amount(alice, swap, idx): + amount = swap.balanceOf(alice) + + expected = swap.calc_withdraw_one_coin(amount, idx) + with boa.reverts(): + swap.remove_liquidity_one_coin(amount, idx, expected + 1, sender=alice) + + +@pytest.mark.parametrize("idx", range(2)) +def test_amount_exceeds_balance(bob, swap, idx): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, idx, 0, sender=bob) + + +def test_below_zero(alice, swap): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, -1, 0, sender=alice) + + +def test_above_n_coins(alice, swap, pool_size): + with boa.reverts(): + swap.remove_liquidity_one_coin(1, pool_size, 0, sender=alice) + + +@pytest.mark.parametrize("idx", range(2)) +def test_event(alice, bob, swap, idx, pool_type): + swap.transfer(bob, 10**18, sender=alice) + _, events = call_returning_result_and_logs(swap, "remove_liquidity_one_coin", 10**18, idx, 0, sender=bob) + + if pool_type == 0: + assert f"RemoveLiquidityOne(provider={bob}" in repr(events[2]) + else: + assert f"RemoveLiquidityOne(provider={bob}" in repr(events[3]) From bc5c37a0bd96567c6ede8b87db454f38d37e208c Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 19 Jan 2024 11:44:09 +0100 Subject: [PATCH 295/337] Only run fixtures when used --- tests/conftest.py | 20 ++-- tests/fixtures/accounts.py | 20 ++-- tests/fixtures/mocks.py | 2 +- tests/fixtures/pools.py | 9 +- tests/fixtures/tokens.py | 28 ++++- .../pools/exchange/test_exchange_received.py | 2 +- tests/pools/exchange/test_exchange_reverts.py | 5 +- tests/pools/oracle/test_oracle.py | 105 +++++++++--------- 8 files changed, 112 insertions(+), 79 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 26d71998..dfc47a6d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,11 +40,11 @@ def pytest_generate_tests(metafunc): if "metapool_token_type" in metafunc.fixturenames: # for meta pool only 1st coin is selected - token_type_items = sorted(TOKEN_TYPES.items()) + token_type_items = get_tokens_for_metafunc(metafunc) metafunc.parametrize( "metapool_token_type", - [v for k, v in token_type_items], - ids=[f"(MetaTokenType={k})" for k, v in token_type_items], + [number for name, number in token_type_items], + ids=[f"(MetaTokenType={name})" for name, number in token_type_items], ) if "initial_decimals" in metafunc.fixturenames: @@ -57,11 +57,7 @@ def get_pool_token_pairs(metafunc): if metafunc.definition.get_closest_marker(f"only_{name}_tokens"): return [((name, number), (name, number))] - items = [ - (name, number) - for name, number in TOKEN_TYPES.items() - if not metafunc.definition.get_closest_marker(f"skip_{name}_tokens") - ] + items = get_tokens_for_metafunc(metafunc) # make all combinations possible all_combinations = list(combinations_with_replacement(items, 2)) # make sure we get the same result in each worker @@ -70,6 +66,14 @@ def get_pool_token_pairs(metafunc): return sorted(random.sample(all_combinations, k=2)) +def get_tokens_for_metafunc(metafunc): + return [ + (name, number) + for name, number in TOKEN_TYPES.items() + if not metafunc.definition.get_closest_marker(f"skip_{name}_tokens") + ] + + @pytest.fixture(scope="session") def pool_size(): return 2 diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index e9f679e0..f519d9e4 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -6,6 +6,7 @@ from tests.utils.tokens import mint_for_testing +from ..constants import POOL_TYPES from .constants import INITIAL_AMOUNT @@ -66,9 +67,9 @@ def accounts(bob, charlie, dave, erin, frank): # <--------------------- Functions ---------------------> def mint_account(account, pool_tokens, initial_balance, initial_amounts): - mint_for_testing(account, initial_balance, None, True) + mint_for_testing(account, initial_balance, token_contract=None, mint_eth=True) for pool_token, amount in zip(pool_tokens, initial_amounts): - mint_for_testing(account, amount, pool_token, False) + mint_for_testing(account, amount, pool_token, mint_eth=False) def approve_account(account, pool_tokens, swap): @@ -196,7 +197,9 @@ def approve_meta_bob(bob, underlying_tokens, swap): @pytest.fixture() -def basic_setup(alice, bob, mint_alice, deposit_amounts, basic_swap, initial_balance, initial_amounts, pool_tokens): +def basic_setup( + alice, approve_alice, bob, mint_alice, deposit_amounts, basic_swap, initial_balance, initial_amounts, pool_tokens +): mint_for_testing(bob, 1 * 10**18, None, True) with boa.env.prank(alice): @@ -250,7 +253,10 @@ def meta_setup( @pytest.fixture() -def initial_setup(meta_setup, basic_setup, pool_type): - if pool_type == 0: - return basic_setup - return meta_setup +def initial_setup(pool_type, request): + """ + Set up the initial state for a pool test. + Run either basic_setup or meta_setup depending on the pool_type. + """ + fixture_name = {POOL_TYPES["basic"]: "basic_setup", POOL_TYPES["meta"]: "meta_setup"}[pool_type] + return request.getfixturevalue(fixture_name) diff --git a/tests/fixtures/mocks.py b/tests/fixtures/mocks.py index 723c7765..148b1b9a 100644 --- a/tests/fixtures/mocks.py +++ b/tests/fixtures/mocks.py @@ -2,7 +2,7 @@ import pytest -@pytest.fixture(scope="module") +@pytest.fixture() def callback_contract(bob, swap, pool_tokens, underlying_tokens, callback_swap_deployer): with boa.env.prank(bob): callback = callback_swap_deployer.deploy(swap.address, bob) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 3dce40c2..52ee6f9d 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -2,12 +2,16 @@ import pytest from eth_utils import function_signature_to_4byte_selector +from tests.constants import POOL_TYPES + ORACLE_METHOD_ID = function_signature_to_4byte_selector("exchangeRate()") OFFPEG_FEE_MULTIPLIER = 20000000000 @pytest.fixture() def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_deployer, set_pool_implementations): + # factory, set_metapool_implementations, zero_address, metapool_token, base_pool, meta_deployer, add_base_pool + A = 2000 fee = 1000000 method_ids = [b""] * pool_size @@ -93,8 +97,9 @@ def meta_swap( @pytest.fixture() -def swap(basic_swap, meta_swap, pool_type): - return {0: basic_swap, 1: meta_swap}[pool_type] +def swap(request, pool_type): + fixture_name = {POOL_TYPES["basic"]: "basic_swap", POOL_TYPES["meta"]: "meta_swap"}[pool_type] + return request.getfixturevalue(fixture_name) # <--------------------- Metapool configuration ---------------------> diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index d6cc8f1e..f2a16c72 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -1,6 +1,8 @@ import boa import pytest +from tests.constants import TOKEN_TYPES + @pytest.fixture() def plain_tokens(erc20_deployer, deployer, decimals): @@ -18,7 +20,7 @@ def oracle_tokens(erc20oracle_deployer, deployer, decimals): @pytest.fixture() -def rebase_tokens(erc20_rebasing_deployer, deployer, decimals): +def rebasing_tokens(erc20_rebasing_deployer, deployer, decimals): with boa.env.prank(deployer): return [ erc20_rebasing_deployer.deploy(f"OR_TKN{i}", f"OR_TKN{i}", decimals[i], True) @@ -27,15 +29,29 @@ def rebase_tokens(erc20_rebasing_deployer, deployer, decimals): @pytest.fixture() -def pool_tokens(pool_token_types, plain_tokens, oracle_tokens, rebase_tokens): - tokens = {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens} - return [tokens[t][i] for i, t in enumerate(pool_token_types)] +def pool_tokens(pool_token_types, request): + fixtures = { + TOKEN_TYPES["plain"]: "plain_tokens", + TOKEN_TYPES["oracle"]: "oracle_tokens", + TOKEN_TYPES["rebasing"]: "rebasing_tokens", + } + type1, type2 = pool_token_types + first, _ = request.getfixturevalue(fixtures[type1]) + _, second = request.getfixturevalue(fixtures[type2]) + return first, second # <--------------------- Metapool configuration ---------------------> @pytest.fixture() -def metapool_token(metapool_token_type, plain_tokens, oracle_tokens, rebase_tokens): - return {0: plain_tokens, 1: oracle_tokens, 2: rebase_tokens}[metapool_token_type][0] +def metapool_token(metapool_token_type, request, initial_decimals): + assert initial_decimals, "Fixture required for requesting `decimals` downstream" + fixture = { + TOKEN_TYPES["plain"]: "plain_tokens", + TOKEN_TYPES["oracle"]: "oracle_tokens", + TOKEN_TYPES["rebasing"]: "rebasing_tokens", + } + metapool_token, _ = request.getfixturevalue(fixture[metapool_token_type]) + return metapool_token @pytest.fixture() diff --git a/tests/pools/exchange/test_exchange_received.py b/tests/pools/exchange/test_exchange_received.py index fa642c9a..1d768b9c 100644 --- a/tests/pools/exchange/test_exchange_received.py +++ b/tests/pools/exchange/test_exchange_received.py @@ -98,8 +98,8 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): return _transfer_and_swap -@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) @pytest.mark.skip_rebasing_tokens +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) def test_exchange_received_nonrebasing(bob, swap, pool_tokens, sending, receiving, transfer_and_swap): swap_data = transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/pools/exchange/test_exchange_reverts.py b/tests/pools/exchange/test_exchange_reverts.py index 86c7f739..a39bf515 100644 --- a/tests/pools/exchange/test_exchange_reverts.py +++ b/tests/pools/exchange/test_exchange_reverts.py @@ -12,11 +12,8 @@ def test_insufficient_balance(charlie, pool_tokens, underlying_tokens, swap, sen assert token.balanceOf(charlie) == 0 # Charlie doesn't have any tokens, all balances are 0 - try: + with boa.reverts(): swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) - assert False - except: # noqa: E722 - assert True @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index 09163210..849f7fb5 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -1,6 +1,7 @@ import boa import pytest +from tests.constants import POOL_TYPES, TOKEN_TYPES from tests.fixtures.accounts import add_base_pool_liquidity, mint_account from tests.fixtures.constants import INITIAL_AMOUNT from tests.utils.tokens import mint_for_testing @@ -9,14 +10,27 @@ @pytest.fixture() -def initial_setup_alice(pool_type, basic_setup_alice, meta_setup_alice): - if pool_type == 0: - return basic_setup_alice - return meta_setup_alice +def initial_setup_alice(pool_type, request): + """ + Set up the initial state for Alice. + Run either the basic or meta fixture depending on the pool type. + """ + fixtures = {POOL_TYPES["basic"]: "basic_setup_alice", POOL_TYPES["meta"]: "meta_setup_alice"} + return request.getfixturevalue(fixtures[pool_type]) @pytest.fixture() -def basic_setup_alice(alice, initial_amounts, initial_balance, oracle_tokens, basic_swap): +def basic_setup_alice( + alice, + initial_amounts, + initial_balance, + oracle_tokens, + basic_swap, + base_pool_tokens, + base_pool, + base_pool_decimals, + underlying_tokens, +): mint_for_testing(alice, 1 * 10**18, None, True) mint_account(alice, oracle_tokens, initial_balance, initial_amounts) with boa.env.prank(alice): @@ -48,22 +62,22 @@ def test_initial_liquidity( oracle_tokens, metapool_token, ): - amounts = [] - if pool_type == 0: - for i, t in enumerate(pool_token_types): - if t != 1: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) - else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate()) + amounts = [ + DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate() + if t == TOKEN_TYPES["oracle"] + else DEPOSIT_AMOUNT * 10 ** decimals[i] + for i, t in enumerate(pool_token_types) + ] else: - if metapool_token_type == 1: - amounts = [ + amounts = ( + [ DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), DEPOSIT_AMOUNT * 10**18, ] - else: - amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + if metapool_token_type == 1 + else [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + ) swap.add_liquidity(amounts, 0, sender=alice) swap.add_liquidity(amounts, 0, sender=alice) @@ -76,44 +90,35 @@ def test_oracles(alice, swap, pool_size): assert swap._immutables.rate_oracles.get() != [0] * pool_size -def test_get_dy( - alice, - initial_setup_alice, - swap, - pool_type, - pool_token_types, - metapool_token_type, - decimals, - meta_decimals, - oracle_tokens, - metapool_token, +def test_get_dy_basic( + alice, initial_setup_alice, basic_swap, pool_token_types, decimals, meta_decimals, oracle_tokens, metapool_token ): - amounts = [] + amounts = [ + DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate() + if t == 1 + else DEPOSIT_AMOUNT * 10 ** decimals[i] + for i, t in enumerate(pool_token_types) + ] - if pool_type == 0: - for i, t in enumerate(pool_token_types): - if t != 1: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i]) - else: - amounts.append(DEPOSIT_AMOUNT * 10 ** decimals[i] * 10**18 // oracle_tokens[i].exchangeRate()) - else: - if metapool_token_type == 1: - amounts = [ - DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), - DEPOSIT_AMOUNT * 10**18, - ] - else: - amounts = [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + basic_swap.add_liquidity(amounts, 0, sender=alice) - swap.add_liquidity(amounts, 0, sender=alice) + rate_1 = 10**18 if pool_token_types[0] != 1 else oracle_tokens[0].exchangeRate() + rate_2 = 10**18 if pool_token_types[1] != 1 else oracle_tokens[1].exchangeRate() - if pool_type == 0: - rate_1 = 10**18 if pool_token_types[0] != 1 else oracle_tokens[0].exchangeRate() - rate_2 = 10**18 if pool_token_types[1] != 1 else oracle_tokens[1].exchangeRate() + assert basic_swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) - assert swap.get_dy(0, 1, rate_2) == pytest.approx(rate_1, rel=1e-3) - else: - rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() +def test_get_dy_meta( + alice, initial_setup_alice, meta_swap, metapool_token_type, decimals, meta_decimals, oracle_tokens, metapool_token +): + amounts = ( + [DEPOSIT_AMOUNT * 10**meta_decimals * 10**18 // metapool_token.exchangeRate(), DEPOSIT_AMOUNT * 10**18] + if metapool_token_type == 1 + else [DEPOSIT_AMOUNT * 10**meta_decimals, DEPOSIT_AMOUNT * 10**18] + ) + + meta_swap.add_liquidity(amounts, 0, sender=alice) + + rate_1 = 1 if metapool_token_type != 1 else metapool_token.exchangeRate() - assert swap.get_dy(0, 1, 10**18) == pytest.approx(rate_1, rel=1e-3) + assert meta_swap.get_dy(0, 1, 10**18) == pytest.approx(rate_1, rel=1e-3) From d61b1b61cf3bbb702d3a5894d493ead81ab24d5c Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 19 Jan 2024 15:10:24 +0100 Subject: [PATCH 296/337] =?UTF-8?q?Debugging=20=C2=AF\=5F(=E3=83=84)=5F/?= =?UTF-8?q?=C2=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 12 ++++++++---- tests/fixtures/accounts.py | 15 +++++++++++++-- tests/fixtures/pools.py | 18 ++++++++++++++---- tests/fixtures/tokens.py | 5 +++-- tests/pools/exchange/test_exchange_reverts.py | 2 +- tests/pools/general/test_oracles.py | 9 ++++++++- tests/pools/general/test_swap_getters.py | 4 +++- tests/pools/meta/test_exchange_underlying.py | 14 +++++++------- tests/pools/oracle/test_oracle.py | 12 ++++++++---- 9 files changed, 65 insertions(+), 26 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dfc47a6d..a62e6d61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,13 +53,13 @@ def pytest_generate_tests(metafunc): def get_pool_token_pairs(metafunc): - for name, number in TOKEN_TYPES.items(): - if metafunc.definition.get_closest_marker(f"only_{name}_tokens"): - return [((name, number), (name, number))] - items = get_tokens_for_metafunc(metafunc) # make all combinations possible all_combinations = list(combinations_with_replacement(items, 2)) + + if len(all_combinations) < 2: + return all_combinations + # make sure we get the same result in each worker random = Random(len(metafunc.fixturenames)) # take 2 combinations for smaller test set @@ -67,6 +67,10 @@ def get_pool_token_pairs(metafunc): def get_tokens_for_metafunc(metafunc): + for name, number in TOKEN_TYPES.items(): + if metafunc.definition.get_closest_marker(f"only_{name}_tokens"): + return [(name, number)] + return [ (name, number) for name, number in TOKEN_TYPES.items() diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index f519d9e4..6f6ac746 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -198,8 +198,18 @@ def approve_meta_bob(bob, underlying_tokens, swap): @pytest.fixture() def basic_setup( - alice, approve_alice, bob, mint_alice, deposit_amounts, basic_swap, initial_balance, initial_amounts, pool_tokens + alice, + approve_alice, + bob, + mint_alice, + deposit_amounts, + basic_swap, + initial_balance, + initial_amounts, + pool_tokens, + metapool_token_type, ): + assert metapool_token_type is not None, "Fixture required downstream" mint_for_testing(bob, 1 * 10**18, None, True) with boa.env.prank(alice): @@ -253,10 +263,11 @@ def meta_setup( @pytest.fixture() -def initial_setup(pool_type, request): +def initial_setup(pool_type, request, metapool_token_type): """ Set up the initial state for a pool test. Run either basic_setup or meta_setup depending on the pool_type. """ + assert metapool_token_type is not None, "Fixture required downstream" fixture_name = {POOL_TYPES["basic"]: "basic_setup", POOL_TYPES["meta"]: "meta_setup"}[pool_type] return request.getfixturevalue(fixture_name) diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 52ee6f9d..3767f71d 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -10,8 +10,6 @@ @pytest.fixture() def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_deployer, set_pool_implementations): - # factory, set_metapool_implementations, zero_address, metapool_token, base_pool, meta_deployer, add_base_pool - A = 2000 fee = 1000000 method_ids = [b""] * pool_size @@ -55,8 +53,17 @@ def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_depl @pytest.fixture() def meta_swap( - factory, set_metapool_implementations, zero_address, metapool_token, base_pool, meta_deployer, add_base_pool + factory, + set_metapool_implementations, + zero_address, + metapool_token, + base_pool, + meta_deployer, + add_base_pool, + pool_token_types, ): + assert pool_token_types, "Fixture required downstream" + A = 2000 fee = 1000000 method_id = bytes(b"") @@ -97,7 +104,10 @@ def meta_swap( @pytest.fixture() -def swap(request, pool_type): +def swap(request, pool_type, pool_token_types, initial_decimals, metapool_token_type): + assert all( + fixture is not None for fixture in (initial_decimals, pool_token_types, metapool_token_type) + ), "Fixtures required downstream" fixture_name = {POOL_TYPES["basic"]: "basic_swap", POOL_TYPES["meta"]: "meta_swap"}[pool_type] return request.getfixturevalue(fixture_name) diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index f2a16c72..882b04f1 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -29,7 +29,8 @@ def rebasing_tokens(erc20_rebasing_deployer, deployer, decimals): @pytest.fixture() -def pool_tokens(pool_token_types, request): +def pool_tokens(pool_token_types, request, initial_decimals): + assert initial_decimals, "Fixture required for requesting `decimals` downstream" fixtures = { TOKEN_TYPES["plain"]: "plain_tokens", TOKEN_TYPES["oracle"]: "oracle_tokens", @@ -38,7 +39,7 @@ def pool_tokens(pool_token_types, request): type1, type2 = pool_token_types first, _ = request.getfixturevalue(fixtures[type1]) _, second = request.getfixturevalue(fixtures[type2]) - return first, second + return [first, second] # <--------------------- Metapool configuration ---------------------> diff --git a/tests/pools/exchange/test_exchange_reverts.py b/tests/pools/exchange/test_exchange_reverts.py index a39bf515..45850ba1 100644 --- a/tests/pools/exchange/test_exchange_reverts.py +++ b/tests/pools/exchange/test_exchange_reverts.py @@ -12,7 +12,7 @@ def test_insufficient_balance(charlie, pool_tokens, underlying_tokens, swap, sen assert token.balanceOf(charlie) == 0 # Charlie doesn't have any tokens, all balances are 0 - with boa.reverts(): + with boa.reverts(), boa.env.prank(charlie): swap.exchange(sending, receiving, amount + 1, 0, sender=charlie) diff --git a/tests/pools/general/test_oracles.py b/tests/pools/general/test_oracles.py index 8ed30321..413eee23 100644 --- a/tests/pools/general/test_oracles.py +++ b/tests/pools/general/test_oracles.py @@ -44,7 +44,10 @@ def check_oracle(swap, dt): assert approx(swap.price_oracle(n), p1, 1e-5) -@given(amount=strategy("uint256", min_value=1, max_value=10**6)) +@given( + amount=strategy("uint256", min_value=1, max_value=10**6), + suppress_health_check=HealthCheck.function_scoped_fixture, +) @settings(**SETTINGS) def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): i, j = random.sample(range(swap.N_COINS()), 2) @@ -80,6 +83,7 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): @@ -101,6 +105,7 @@ def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_remove_one(swap, alice, amount, dt0, dt): @@ -118,6 +123,7 @@ def test_price_ema_remove_one(swap, alice, amount, dt0, dt): frac=strategy("uint256", min_value=1, max_value=8), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amounts, frac): @@ -164,6 +170,7 @@ def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimal amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt, math_implementation): diff --git a/tests/pools/general/test_swap_getters.py b/tests/pools/general/test_swap_getters.py index 771e1103..37081f75 100644 --- a/tests/pools/general/test_swap_getters.py +++ b/tests/pools/general/test_swap_getters.py @@ -1,6 +1,6 @@ import pytest from boa.test import strategy -from hypothesis import given, settings +from hypothesis import HealthCheck, given, settings SETTINGS = {"max_examples": 100, "deadline": None} @@ -9,6 +9,7 @@ amount_in=strategy("decimal", min_value=0.001, max_value=10**6), i=strategy("uint", min_value=0, max_value=2), j=strategy("uint", min_value=0, max_value=2), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_get_dx(i, j, amount_in, swap, factory, initial_setup): @@ -30,6 +31,7 @@ def test_get_dx(i, j, amount_in, swap, factory, initial_setup): amount_in=strategy("decimal", min_value=0.001, max_value=10**6), i=strategy("uint", min_value=0, max_value=4), j=strategy("uint", min_value=0, max_value=4), + suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_get_dx_underlying(i, j, amount_in, meta_swap, factory, initial_setup): diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 10cb0f3b..0cad2044 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -9,13 +9,13 @@ @pytest.mark.only_plain_tokens @pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) -def test_amounts(bob, meta_swap, metapool_token, sending, receiving, meta_decimals, base_pool_decimals): +def test_amounts(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals - metapool_token = [metapool_token[0], *metapool_token[2:]] + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] amount_sent = 10 ** underlying_decimals[sending] - if sending > 0 and metapool_token[sending].balanceOf(bob) < amount_sent: - metapool_token[sending]._mint_for_testing(bob, amount_sent) + if sending > 0 and underlying_tokens[sending].balanceOf(bob) < amount_sent: + underlying_tokens[sending]._mint_for_testing(bob, amount_sent) expected_received = meta_swap.get_dy_underlying(sending, receiving, amount_sent) received_true = meta_swap.exchange_underlying(sending, receiving, amount_sent, 0, sender=bob) # noqa: F841 @@ -24,11 +24,11 @@ def test_amounts(bob, meta_swap, metapool_token, sending, receiving, meta_decima @pytest.mark.only_plain_tokens @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_min_dy_underlying(bob, meta_swap, metapool_token, sending, receiving, meta_decimals, base_pool_decimals): +def test_min_dy_underlying(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals - metapool_token = [metapool_token[0], *metapool_token[2:]] + underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] amount = 10 ** underlying_decimals[sending] - metapool_token[sending]._mint_for_testing(bob, amount, sender=bob) + underlying_tokens[sending]._mint_for_testing(bob, amount, sender=bob) expected = meta_swap.get_dy_underlying(sending, receiving, amount) received = meta_swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) diff --git a/tests/pools/oracle/test_oracle.py b/tests/pools/oracle/test_oracle.py index 849f7fb5..c2f04997 100644 --- a/tests/pools/oracle/test_oracle.py +++ b/tests/pools/oracle/test_oracle.py @@ -7,6 +7,7 @@ from tests.utils.tokens import mint_for_testing DEPOSIT_AMOUNT = INITIAL_AMOUNT // 100 +pytestmark = pytest.mark.only_oracle_tokens @pytest.fixture() @@ -31,7 +32,7 @@ def basic_setup_alice( base_pool_decimals, underlying_tokens, ): - mint_for_testing(alice, 1 * 10**18, None, True) + mint_for_testing(alice, amount=1 * 10**18, token_contract=None, mint_eth=True) mint_account(alice, oracle_tokens, initial_balance, initial_amounts) with boa.env.prank(alice): for token in oracle_tokens: @@ -42,7 +43,7 @@ def basic_setup_alice( def meta_setup_alice( alice, base_pool_tokens, base_pool, base_pool_decimals, initial_amounts, meta_swap, underlying_tokens ): - mint_for_testing(alice, 1 * 10**18, None, True) + mint_for_testing(alice, amount=1 * 10**18, token_contract=None, mint_eth=True) add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) with boa.env.prank(alice): @@ -86,8 +87,11 @@ def test_initial_liquidity( assert swap.admin_balances(1) == 0 -def test_oracles(alice, swap, pool_size): - assert swap._immutables.rate_oracles.get() != [0] * pool_size +def test_oracles(alice, swap, pool_size, pool_type): + if pool_type == POOL_TYPES["basic"]: + assert swap._immutables.rate_oracles != [0] * pool_size + else: + assert swap._immutables.rate_oracle def test_get_dy_basic( From 0a543545caacd3f8ff5664b0477ebcedba8256ef Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 22 Jan 2024 12:44:16 +0100 Subject: [PATCH 297/337] More fixture unscrambling --- tests/fixtures/accounts.py | 97 ++++++++----------- tests/fixtures/constants.py | 55 ++++++----- tests/fixtures/pools.py | 11 +-- tests/fixtures/tokens.py | 4 +- tests/pools/general/test_oracles.py | 16 +-- tests/pools/general/test_swap_getters.py | 4 +- .../pools/liquidity/test_initial_liquidity.py | 29 +++--- tests/pools/meta/test_exchange_underlying.py | 8 +- .../meta/test_exchange_underlying_reverts.py | 2 +- tests/utils/__init__.py | 2 +- 10 files changed, 98 insertions(+), 130 deletions(-) diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index 6f6ac746..b53a9380 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -121,72 +121,54 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal @pytest.fixture() -def add_initial_liquidity_owner( +def add_initial_liquidity_owner_basic( owner, approve_owner, mint_owner, deposit_amounts, - swap, - pool_type, + basic_swap, underlying_tokens, base_pool, base_pool_tokens, base_pool_decimals, base_pool_lp_token, ): - if pool_type == 0: - with boa.env.prank(owner): - swap.add_liquidity(deposit_amounts, 0) - else: - add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) - with boa.env.prank(owner): - base_pool_lp_token.approve(swap.address, 2**256 - 1) - lp_token_bal = base_pool_lp_token.balanceOf(owner) - to_mint_token0 = lp_token_bal * 10 ** underlying_tokens[0].decimals() // 10 ** base_pool_lp_token.decimals() - - mint_for_testing(owner, to_mint_token0, underlying_tokens[0], False) - underlying_tokens[0].approve(swap.address, 2**256 - 1) - - swap.add_liquidity([to_mint_token0, lp_token_bal], 0) + with boa.env.prank(owner): + basic_swap.add_liquidity(deposit_amounts, 0) @pytest.fixture() -def add_initial_liquidity_alice( - alice, - approve_alice, - mint_alice, - deposit_amounts, - swap, - pool_type, +def add_initial_liquidity_owner_meta( + owner, + approve_owner, + mint_owner, + deposit_meta_amounts, + meta_swap, + metapool_token, base_pool, base_pool_tokens, base_pool_decimals, base_pool_lp_token, ): - if pool_type == 0: - with boa.env.prank(alice): - swap.add_liquidity(deposit_amounts, 0) - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - with boa.env.prank(alice): - base_pool_lp_token.approve(swap.address, 2**256 - 1) - swap.add_liquidity(deposit_amounts, 0) + add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) + with boa.env.prank(owner): + base_pool_lp_token.approve(meta_swap.address, 2**256 - 1) + lp_token_bal = base_pool_lp_token.balanceOf(owner) + to_mint_token0 = lp_token_bal * 10 ** metapool_token.decimals() // 10 ** base_pool_lp_token.decimals() + + mint_for_testing(owner, to_mint_token0, metapool_token, False) + metapool_token.approve(meta_swap.address, 2**256 - 1) + + meta_swap.add_liquidity([to_mint_token0, lp_token_bal], 0) @pytest.fixture() -def mint_meta_bob( - bob, - mint_bob, - base_pool, - base_pool_tokens, - base_pool_decimals, - underlying_tokens, - initial_amounts, - base_pool_lp_token, -): - add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(bob, initial_amounts[0], underlying_tokens[0], False) - assert underlying_tokens[0].balanceOf(bob) == base_pool_lp_token.balanceOf(bob) +def add_initial_liquidity_owner(pool_type, request): + fixture_name = { + POOL_TYPES["basic"]: "add_initial_liquidity_owner_basic", + POOL_TYPES["meta"]: "add_initial_liquidity_owner_meta", + }[pool_type] + return request.getfixturevalue(fixture_name) @pytest.fixture() @@ -199,21 +181,21 @@ def approve_meta_bob(bob, underlying_tokens, swap): @pytest.fixture() def basic_setup( alice, - approve_alice, bob, mint_alice, - deposit_amounts, + deposit_basic_amounts, basic_swap, initial_balance, initial_amounts, pool_tokens, metapool_token_type, ): + approve_account(alice, pool_tokens, basic_swap) assert metapool_token_type is not None, "Fixture required downstream" mint_for_testing(bob, 1 * 10**18, None, True) with boa.env.prank(alice): - basic_swap.add_liquidity(deposit_amounts, 0) + basic_swap.add_liquidity(deposit_basic_amounts, 0) mint_account(bob, pool_tokens, initial_balance, initial_amounts) with boa.env.prank(bob): @@ -225,17 +207,20 @@ def basic_setup( def meta_setup( alice, bob, - mint_alice, - approve_alice, - deposit_amounts, + deposit_meta_amounts, meta_swap, base_pool, base_pool_tokens, base_pool_decimals, base_pool_lp_token, - initial_amounts, + initial_balance, + meta_initial_amounts, underlying_tokens, + pool_tokens, + add_initial_liquidity_owner_meta, ): + approve_account(alice, pool_tokens, meta_swap) + mint_account(alice, pool_tokens, initial_balance, meta_initial_amounts) mint_for_testing(bob, 1 * 10**18, None, True) underlying_token = underlying_tokens[0] @@ -251,10 +236,10 @@ def meta_setup( with boa.env.prank(alice): underlying_token.approve(meta_swap.address, 2**256 - 1) base_pool_lp_token.approve(meta_swap.address, 2**256 - 1) - meta_swap.add_liquidity(deposit_amounts, 0) + meta_swap.add_liquidity(deposit_meta_amounts, 0) add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(bob, initial_amounts[0], underlying_token, False) + mint_for_testing(bob, initial_balance, underlying_token, False) assert underlying_token.balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) with boa.env.prank(bob): @@ -263,11 +248,11 @@ def meta_setup( @pytest.fixture() -def initial_setup(pool_type, request, metapool_token_type): +def initial_setup(pool_type, request, metapool_token_type, pool_token_types, initial_decimals): """ Set up the initial state for a pool test. Run either basic_setup or meta_setup depending on the pool_type. """ - assert metapool_token_type is not None, "Fixture required downstream" + assert metapool_token_type is not None and pool_token_types and initial_decimals, "Fixtures required downstream" fixture_name = {POOL_TYPES["basic"]: "basic_setup", POOL_TYPES["meta"]: "meta_setup"}[pool_type] return request.getfixturevalue(fixture_name) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index 66ff1091..c88fe092 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -9,38 +9,43 @@ def initial_balance() -> int: @pytest.fixture() -def initial_amounts(pool_type, decimals, meta_decimals) -> list[int]: - return ( - [INITIAL_AMOUNT * 10**precision for precision in decimals] - if pool_type == 0 - else [INITIAL_AMOUNT * 10**meta_decimals, INITIAL_AMOUNT * 10**18] - ) +def meta_initial_amounts(meta_decimals) -> list[int]: + return [INITIAL_AMOUNT * 10**meta_decimals, INITIAL_AMOUNT * 10**18] @pytest.fixture() -def deposit_amounts( - initial_amounts: list[int], pool_type, pool_token_types, metapool_token_type, pool_tokens, underlying_tokens +def initial_amounts(pool_type, decimals, meta_initial_amounts) -> list[int]: + return [INITIAL_AMOUNT * 10**precision for precision in decimals] if pool_type == 0 else meta_initial_amounts + + +@pytest.fixture() +def deposit_basic_amounts(initial_amounts: list[int], pool_token_types, pool_tokens) -> list[int]: + return [ + initial_amounts[i] * 10**18 // pool_token.exchangeRate() // 2 + if pool_token_type == 1 + else initial_amounts[i] // 2 + for i, (pool_token_type, pool_token) in enumerate(zip(pool_token_types, pool_tokens)) + ] + + +@pytest.fixture() +def deposit_meta_amounts( + meta_initial_amounts: list[int], metapool_token_type, pool_tokens, underlying_tokens ) -> list[int]: - amounts = [] + return [ + meta_initial_amounts[0] // 2 + if metapool_token_type != 1 + else meta_initial_amounts[0] * 10**18 // underlying_tokens[0].exchangeRate() // 2, + meta_initial_amounts[1] // 2, + ] + +@pytest.fixture() +def deposit_amounts(deposit_basic_amounts, deposit_meta_amounts, pool_type) -> list[int]: # This (almost) adds liquidity in balance for oracle tokens if pool_type == 0: - i = 0 - for ptt, pt in zip(pool_token_types, pool_tokens): - if ptt != 1: - amounts.append(initial_amounts[i] // 2) - else: - amounts.append(initial_amounts[i] * 10**18 // pt.exchangeRate() // 2) - i += 1 - else: - if metapool_token_type != 1: - amounts.append(initial_amounts[0] // 2) - else: - amounts.append(initial_amounts[0] * 10**18 // underlying_tokens[0].exchangeRate() // 2) - - amounts.append(initial_amounts[1] // 2) - - return amounts + return deposit_basic_amounts + return deposit_meta_amounts @pytest.fixture(scope="session") diff --git a/tests/fixtures/pools.py b/tests/fixtures/pools.py index 3767f71d..64ca9399 100644 --- a/tests/fixtures/pools.py +++ b/tests/fixtures/pools.py @@ -53,17 +53,8 @@ def basic_swap(deployer, factory, pool_size, pool_tokens, zero_address, amm_depl @pytest.fixture() def meta_swap( - factory, - set_metapool_implementations, - zero_address, - metapool_token, - base_pool, - meta_deployer, - add_base_pool, - pool_token_types, + factory, set_metapool_implementations, zero_address, metapool_token, base_pool, meta_deployer, add_base_pool ): - assert pool_token_types, "Fixture required downstream" - A = 2000 fee = 1000000 method_id = bytes(b"") diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 882b04f1..1d24d2a8 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -44,8 +44,8 @@ def pool_tokens(pool_token_types, request, initial_decimals): # <--------------------- Metapool configuration ---------------------> @pytest.fixture() -def metapool_token(metapool_token_type, request, initial_decimals): - assert initial_decimals, "Fixture required for requesting `decimals` downstream" +def metapool_token(metapool_token_type, request, initial_decimals, pool_token_types): + assert initial_decimals and pool_token_types, "Fixtures required for requesting `decimals` downstream" fixture = { TOKEN_TYPES["plain"]: "plain_tokens", TOKEN_TYPES["oracle"]: "oracle_tokens", diff --git a/tests/pools/general/test_oracles.py b/tests/pools/general/test_oracles.py index 413eee23..4c930c9c 100644 --- a/tests/pools/general/test_oracles.py +++ b/tests/pools/general/test_oracles.py @@ -9,7 +9,7 @@ from tests.utils import approx from tests.utils.tokens import mint_for_testing -SETTINGS = {"max_examples": 1000, "deadline": None} +SETTINGS = {"max_examples": 100, "deadline": None, "suppress_health_check": [HealthCheck.function_scoped_fixture]} pytestmark = pytest.mark.usefixtures("initial_setup") @@ -44,10 +44,7 @@ def check_oracle(swap, dt): assert approx(swap.price_oracle(n), p1, 1e-5) -@given( - amount=strategy("uint256", min_value=1, max_value=10**6), - suppress_health_check=HealthCheck.function_scoped_fixture, -) +@given(amount=strategy("uint256", min_value=1, max_value=10**6)) @settings(**SETTINGS) def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): i, j = random.sample(range(swap.N_COINS()), 2) @@ -83,7 +80,6 @@ def test_get_p(swap, views_implementation, bob, pool_tokens, decimals, amount): amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt): @@ -105,7 +101,6 @@ def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_remove_one(swap, alice, amount, dt0, dt): @@ -123,7 +118,6 @@ def test_price_ema_remove_one(swap, alice, amount, dt0, dt): frac=strategy("uint256", min_value=1, max_value=8), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amounts, frac): @@ -138,10 +132,7 @@ def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amo check_oracle(swap, dt) -@given( - amount=strategy("uint256", min_value=10**9, max_value=10**15), - suppress_health_check=HealthCheck.function_scoped_fixture, -) +@given(amount=strategy("uint256", min_value=10**9, max_value=10**15)) @settings(**SETTINGS) def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimals, amount): # calc amount in: @@ -170,7 +161,6 @@ def test_manipulate_ema(basic_swap, bob, pool_tokens, underlying_tokens, decimal amount=strategy("uint256", min_value=1, max_value=10**5), dt0=strategy("uint256", min_value=0, max_value=10**6), dt=strategy("uint256", min_value=0, max_value=10**6), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_D_ema(swap, bob, pool_tokens, underlying_tokens, decimals, amount, dt0, dt, math_implementation): diff --git a/tests/pools/general/test_swap_getters.py b/tests/pools/general/test_swap_getters.py index 37081f75..17f6aa4b 100644 --- a/tests/pools/general/test_swap_getters.py +++ b/tests/pools/general/test_swap_getters.py @@ -2,14 +2,13 @@ from boa.test import strategy from hypothesis import HealthCheck, given, settings -SETTINGS = {"max_examples": 100, "deadline": None} +SETTINGS = {"max_examples": 100, "deadline": None, "suppress_health_check": [HealthCheck.function_scoped_fixture]} @given( amount_in=strategy("decimal", min_value=0.001, max_value=10**6), i=strategy("uint", min_value=0, max_value=2), j=strategy("uint", min_value=0, max_value=2), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_get_dx(i, j, amount_in, swap, factory, initial_setup): @@ -31,7 +30,6 @@ def test_get_dx(i, j, amount_in, swap, factory, initial_setup): amount_in=strategy("decimal", min_value=0.001, max_value=10**6), i=strategy("uint", min_value=0, max_value=4), j=strategy("uint", min_value=0, max_value=4), - suppress_health_check=HealthCheck.function_scoped_fixture, ) @settings(**SETTINGS) def test_get_dx_underlying(i, j, amount_in, meta_swap, factory, initial_setup): diff --git a/tests/pools/liquidity/test_initial_liquidity.py b/tests/pools/liquidity/test_initial_liquidity.py index f09e59ec..cf48a36e 100644 --- a/tests/pools/liquidity/test_initial_liquidity.py +++ b/tests/pools/liquidity/test_initial_liquidity.py @@ -5,7 +5,7 @@ from tests.utils.tokens import mint_for_testing -@pytest.fixture(scope="module") +@pytest.fixture() def initial_setup_alice( alice, deposit_amounts, @@ -20,24 +20,21 @@ def initial_setup_alice( pool_tokens, underlying_tokens, ): - with boa.env.anchor(): - mint_for_testing(alice, 1 * 10**18, None, True) + mint_for_testing(alice, 1 * 10**18, None, True) - if pool_type == 0: - mint_account(alice, pool_tokens, initial_balance, initial_amounts) - with boa.env.prank(alice): - for token in pool_tokens: - token.approve(swap.address, 2**256 - 1) - - else: - add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) + if pool_type == 0: + mint_account(alice, pool_tokens, initial_balance, initial_amounts) + with boa.env.prank(alice): + for token in pool_tokens: + token.approve(swap.address, 2**256 - 1) - with boa.env.prank(alice): - for token in underlying_tokens: - token.approve(swap.address, 2**256 - 1) + else: + add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) + mint_for_testing(alice, initial_amounts[0], underlying_tokens[0], False) - yield + with boa.env.prank(alice): + for token in underlying_tokens: + token.approve(swap.address, 2**256 - 1) @pytest.mark.parametrize("min_amount", [0, 10**18]) diff --git a/tests/pools/meta/test_exchange_underlying.py b/tests/pools/meta/test_exchange_underlying.py index 0cad2044..7b7ec435 100644 --- a/tests/pools/meta/test_exchange_underlying.py +++ b/tests/pools/meta/test_exchange_underlying.py @@ -4,11 +4,13 @@ from tests.utils import approx -pytestmark = pytest.mark.usefixtures("initial_setup") +pytestmark = pytest.mark.usefixtures("meta_setup") + +permutations = list(itertools.permutations(range(4), 2)) # 0,1...3,2 @pytest.mark.only_plain_tokens -@pytest.mark.parametrize("sending,receiving", filter(lambda k: 0 in k, itertools.permutations(range(4), 2))) +@pytest.mark.parametrize("sending,receiving", [p for p in permutations if 0 in p]) def test_amounts(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] @@ -23,7 +25,7 @@ def test_amounts(bob, meta_swap, underlying_tokens, sending, receiving, meta_dec @pytest.mark.only_plain_tokens -@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +@pytest.mark.parametrize("sending,receiving", permutations) def test_min_dy_underlying(bob, meta_swap, underlying_tokens, sending, receiving, meta_decimals, base_pool_decimals): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py index ddd621ba..3c5013ed 100644 --- a/tests/pools/meta/test_exchange_underlying_reverts.py +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -3,7 +3,7 @@ import boa import pytest -pytestmark = pytest.mark.usefixtures("initial_setup") +pytestmark = pytest.mark.usefixtures("meta_setup") @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 8ef59ad0..bf970831 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,7 +1,7 @@ from math import log -def approx(x1: int, x2: int, precision: int, abs_precision=None): +def approx(x1: int, x2: int, precision: float, abs_precision=None): if precision >= 1: return True result = False From ab7de7b60bf6e9daae8e7c3ceb685f9ad717e838 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 22 Jan 2024 15:30:36 +0100 Subject: [PATCH 298/337] Separate fixtures for meta --- .github/workflows/ci.yaml | 2 +- tests/fixtures/accounts.py | 50 ++----- tests/fixtures/constants.py | 9 +- tests/fixtures/tokens.py | 4 +- tests/pools/general/test_fees.py | 138 ++++++++---------- tests/pools/general/test_oracles.py | 4 +- .../meta/test_exchange_underlying_reverts.py | 15 +- 7 files changed, 102 insertions(+), 120 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bf16e43c..17ff5afd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -60,4 +60,4 @@ jobs: WEB3_PROVIDER_URL: ${{ secrets.WEB3_PROVIDER_URL }} run: | source .venv/bin/activate - pytest --exitfirst --numprocesses=auto tests/${{ matrix.name }}/ + pytest --numprocesses=auto tests/${{ matrix.name }}/ diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index b53a9380..bc48f355 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -78,36 +78,6 @@ def approve_account(account, pool_tokens, swap): pool_token.approve(swap.address, 2**256 - 1) -@pytest.fixture() -def mint_owner(owner, pool_tokens, initial_balance, initial_amounts): - mint_account(owner, pool_tokens, initial_balance, initial_amounts) - - -@pytest.fixture() -def approve_owner(owner, pool_tokens, swap): - approve_account(owner, pool_tokens, swap) - - -@pytest.fixture() -def mint_alice(alice, pool_tokens, initial_balance, initial_amounts): - mint_account(alice, pool_tokens, initial_balance, initial_amounts) - - -@pytest.fixture() -def approve_alice(alice, pool_tokens, swap): - approve_account(alice, pool_tokens, swap) - - -@pytest.fixture() -def mint_bob(bob, pool_tokens, initial_balance, initial_amounts): - mint_account(bob, pool_tokens, initial_balance, initial_amounts) - - -@pytest.fixture() -def approve_bob(bob, pool_tokens, swap): - approve_account(bob, pool_tokens, swap) - - # <--------------------- Functions ---------------------> def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): amount = INITIAL_AMOUNT // 3 @@ -123,8 +93,6 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal @pytest.fixture() def add_initial_liquidity_owner_basic( owner, - approve_owner, - mint_owner, deposit_amounts, basic_swap, underlying_tokens, @@ -132,7 +100,12 @@ def add_initial_liquidity_owner_basic( base_pool_tokens, base_pool_decimals, base_pool_lp_token, + pool_tokens, + initial_balance, + basic_initial_amounts, ): + mint_account(owner, pool_tokens, initial_balance, basic_initial_amounts) + approve_account(owner, pool_tokens, basic_swap) with boa.env.prank(owner): basic_swap.add_liquidity(deposit_amounts, 0) @@ -140,8 +113,6 @@ def add_initial_liquidity_owner_basic( @pytest.fixture() def add_initial_liquidity_owner_meta( owner, - approve_owner, - mint_owner, deposit_meta_amounts, meta_swap, metapool_token, @@ -149,7 +120,12 @@ def add_initial_liquidity_owner_meta( base_pool_tokens, base_pool_decimals, base_pool_lp_token, + pool_tokens, + initial_balance, + meta_initial_amounts, ): + mint_account(owner, pool_tokens, initial_balance, meta_initial_amounts) + approve_account(owner, pool_tokens, meta_swap) add_base_pool_liquidity(owner, base_pool, base_pool_tokens, base_pool_decimals) with boa.env.prank(owner): base_pool_lp_token.approve(meta_swap.address, 2**256 - 1) @@ -182,14 +158,14 @@ def approve_meta_bob(bob, underlying_tokens, swap): def basic_setup( alice, bob, - mint_alice, deposit_basic_amounts, basic_swap, initial_balance, - initial_amounts, + basic_initial_amounts, pool_tokens, metapool_token_type, ): + mint_account(alice, pool_tokens, initial_balance, basic_initial_amounts) approve_account(alice, pool_tokens, basic_swap) assert metapool_token_type is not None, "Fixture required downstream" mint_for_testing(bob, 1 * 10**18, None, True) @@ -197,7 +173,7 @@ def basic_setup( with boa.env.prank(alice): basic_swap.add_liquidity(deposit_basic_amounts, 0) - mint_account(bob, pool_tokens, initial_balance, initial_amounts) + mint_account(bob, pool_tokens, initial_balance, basic_initial_amounts) with boa.env.prank(bob): for token in pool_tokens: token.approve(basic_swap.address, 2**256 - 1) diff --git a/tests/fixtures/constants.py b/tests/fixtures/constants.py index c88fe092..a6428dd5 100644 --- a/tests/fixtures/constants.py +++ b/tests/fixtures/constants.py @@ -14,8 +14,13 @@ def meta_initial_amounts(meta_decimals) -> list[int]: @pytest.fixture() -def initial_amounts(pool_type, decimals, meta_initial_amounts) -> list[int]: - return [INITIAL_AMOUNT * 10**precision for precision in decimals] if pool_type == 0 else meta_initial_amounts +def basic_initial_amounts(decimals) -> list[int]: + return [INITIAL_AMOUNT * 10**precision for precision in decimals] + + +@pytest.fixture() +def initial_amounts(pool_type, basic_initial_amounts, meta_initial_amounts) -> list[int]: + return basic_initial_amounts if pool_type == 0 else meta_initial_amounts @pytest.fixture() diff --git a/tests/fixtures/tokens.py b/tests/fixtures/tokens.py index 1d24d2a8..c0d44ab5 100644 --- a/tests/fixtures/tokens.py +++ b/tests/fixtures/tokens.py @@ -2,6 +2,7 @@ import pytest from tests.constants import TOKEN_TYPES +from tests.fixtures.accounts import mint_account @pytest.fixture() @@ -85,7 +86,8 @@ def coin_reward(owner, erc20_deployer): @pytest.fixture() -def coin_reward_a(owner, mint_owner, erc20_deployer): +def coin_reward_a(owner, erc20_deployer, pool_tokens, initial_balance, initial_amounts): + mint_account(owner, pool_tokens, initial_balance, initial_amounts) with boa.env.prank(owner): return erc20_deployer.deploy("CRa", "CRa", 18) diff --git a/tests/pools/general/test_fees.py b/tests/pools/general/test_fees.py index 2187d367..f1f62511 100644 --- a/tests/pools/general/test_fees.py +++ b/tests/pools/general/test_fees.py @@ -3,83 +3,73 @@ pytestmark = pytest.mark.usefixtures("initial_setup") -class TestFees: - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_admin_balances( - self, bob, swap, pool_type, pool_tokens, underlying_tokens, initial_amounts, sending, receiving - ): - for send, recv in [(sending, receiving), (receiving, sending)]: - swap.exchange(send, recv, initial_amounts[send], 0, sender=bob) - - for i in (sending, receiving): - if pool_type == 0: - admin_fee = pool_tokens[i].balanceOf(swap) - swap.balances(i) - assert admin_fee > 0 - else: - admin_fee = underlying_tokens[i].balanceOf(swap) - swap.balances(i) - assert admin_fee > 0 - - @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) - def test_withdraw_one_coin( - self, - alice, - bob, - fee_receiver, - swap, - pool_type, - pool_tokens, - underlying_tokens, - sending, - receiving, - initial_amounts, - ): - swap.exchange(sending, receiving, initial_amounts[sending], 0, sender=bob) - - admin_balance = swap.admin_balances(receiving) - - assert admin_balance > 0 - assert swap.admin_balances(sending) == 0 - - swap.withdraw_admin_fees(sender=alice) - swap_balance = ( - pool_tokens[receiving].balanceOf(swap) if pool_type == 0 else underlying_tokens[receiving].balanceOf(swap) - ) - assert swap.balances(receiving) == swap_balance - expected_balance = ( - pool_tokens[receiving].balanceOf(fee_receiver) - if pool_type == 0 - else underlying_tokens[receiving].balanceOf(fee_receiver) - ) - - assert admin_balance == pytest.approx(expected_balance, abs=1) # +- 1 - - def test_no_fees(self, bob, fee_receiver, swap, pool_type, pool_tokens, underlying_tokens): - swap.withdraw_admin_fees(sender=bob) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_admin_balances(bob, swap, pool_type, pool_tokens, underlying_tokens, initial_amounts, sending, receiving): + for send, recv in [(sending, receiving), (receiving, sending)]: + swap.exchange(send, recv, initial_amounts[send], 0, sender=bob) + for i in (sending, receiving): if pool_type == 0: - for coin in pool_tokens: - assert coin.balanceOf(fee_receiver) == 0 + admin_fee = pool_tokens[i].balanceOf(swap) - swap.balances(i) + assert admin_fee > 0 else: - for coin in underlying_tokens: - assert coin.balanceOf(fee_receiver) == 0 + admin_fee = underlying_tokens[i].balanceOf(swap) - swap.balances(i) + assert admin_fee > 0 - def test_withdraw_admin_fees(self, bob, swap, pool_type, pool_tokens, underlying_tokens, fee_receiver, decimals): - swap.exchange(1, 0, 10_000 * 10 ** decimals[1], 0, sender=bob) - fees = [] - if pool_type == 0: - for i, coin in enumerate(pool_tokens): - assert coin.balanceOf(fee_receiver) == 0 - fees.append(swap.admin_balances(i)) - else: - for i, coin in enumerate(underlying_tokens[:2]): - assert coin.balanceOf(fee_receiver) == 0 - fees.append(swap.admin_balances(i)) +@pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) +def test_withdraw_one_coin( + alice, bob, fee_receiver, swap, pool_type, pool_tokens, underlying_tokens, sending, receiving, initial_amounts +): + swap.exchange(sending, receiving, initial_amounts[sending], 0, sender=bob) - swap.withdraw_admin_fees(sender=bob) - if pool_type == 0: - for i, coin in enumerate(pool_tokens): - assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) - else: - for i, coin in enumerate(underlying_tokens[:2]): - assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) + admin_balance = swap.admin_balances(receiving) + + assert admin_balance > 0 + assert swap.admin_balances(sending) == 0 + + swap.withdraw_admin_fees(sender=alice) + swap_balance = ( + pool_tokens[receiving].balanceOf(swap) if pool_type == 0 else underlying_tokens[receiving].balanceOf(swap) + ) + assert swap.balances(receiving) == swap_balance + expected_balance = ( + pool_tokens[receiving].balanceOf(fee_receiver) + if pool_type == 0 + else underlying_tokens[receiving].balanceOf(fee_receiver) + ) + + assert admin_balance == pytest.approx(expected_balance, abs=1) # +- 1 + + +def test_no_fees(bob, fee_receiver, swap, pool_type, pool_tokens, underlying_tokens): + swap.withdraw_admin_fees(sender=bob) + + if pool_type == 0: + for coin in pool_tokens: + assert coin.balanceOf(fee_receiver) == 0 + else: + for coin in underlying_tokens: + assert coin.balanceOf(fee_receiver) == 0 + + +def test_withdraw_admin_fees(bob, swap, pool_type, pool_tokens, underlying_tokens, fee_receiver, decimals): + swap.exchange(1, 0, 10_000 * 10 ** decimals[1], 0, sender=bob) + + fees = [] + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(fee_receiver) == 0 + fees.append(swap.admin_balances(i)) + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(fee_receiver) == 0 + fees.append(swap.admin_balances(i)) + + swap.withdraw_admin_fees(sender=bob) + if pool_type == 0: + for i, coin in enumerate(pool_tokens): + assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) + else: + for i, coin in enumerate(underlying_tokens[:2]): + assert coin.balanceOf(fee_receiver) == pytest.approx(fees[i], abs=1) diff --git a/tests/pools/general/test_oracles.py b/tests/pools/general/test_oracles.py index 4c930c9c..9b74071b 100644 --- a/tests/pools/general/test_oracles.py +++ b/tests/pools/general/test_oracles.py @@ -104,7 +104,7 @@ def test_price_ema_exchange(swap, bob, pool_tokens, underlying_tokens, decimals, ) @settings(**SETTINGS) def test_price_ema_remove_one(swap, alice, amount, dt0, dt): - i = random.sample(range(swap.N_COINS()), 1)[0] + i = random.choice(range(swap.N_COINS())) alice_lp_bal = swap.balanceOf(alice) amt_to_remove = int(alice_lp_bal * amount / (10**5 - 1)) @@ -121,7 +121,7 @@ def test_price_ema_remove_one(swap, alice, amount, dt0, dt): ) @settings(**SETTINGS) def test_price_ema_remove_imbalance(swap, alice, dt0, dt, pool_size, deposit_amounts, frac): - i = random.sample(range(swap.N_COINS()), 1)[0] + i = random.choice(range(swap.N_COINS())) amounts = [0] * pool_size amounts[i] = deposit_amounts[i] // frac lp_balance = pool_size * deposit_amounts[i] diff --git a/tests/pools/meta/test_exchange_underlying_reverts.py b/tests/pools/meta/test_exchange_underlying_reverts.py index 3c5013ed..078e232d 100644 --- a/tests/pools/meta/test_exchange_underlying_reverts.py +++ b/tests/pools/meta/test_exchange_underlying_reverts.py @@ -3,11 +3,20 @@ import boa import pytest +from tests.constants import TOKEN_TYPES + pytestmark = pytest.mark.usefixtures("meta_setup") +permutations = list(itertools.permutations(range(4), 2)) # 0,1...3,2 + + +@pytest.mark.parametrize("sending,receiving", permutations) +def test_min_dy_too_high( + bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, metapool_token_type +): + if sending == 0 and metapool_token_type == TOKEN_TYPES["rebasing"]: + return pytest.skip("This test does not revert sending rebasing tokens") # TODO -@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_min_dy_too_high(bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving): underlying_decimals = [meta_decimals] + base_pool_decimals underlying_tokens = [underlying_tokens[0], *underlying_tokens[2:]] amount = 10 ** underlying_decimals[sending] @@ -18,7 +27,7 @@ def test_min_dy_too_high(bob, meta_swap, underlying_tokens, meta_decimals, base_ meta_swap.exchange_underlying(sending, receiving, amount, min_dy, sender=bob) -@pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) +@pytest.mark.parametrize("sending,receiving", permutations) def test_insufficient_balance( bob, meta_swap, underlying_tokens, meta_decimals, base_pool_decimals, sending, receiving, zero_address ): From 9e39e58b583a9dcddae6772d5e18f782048195a2 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 22 Jan 2024 16:57:10 +0100 Subject: [PATCH 299/337] Reset env between tests --- tests/conftest.py | 4 +++- tests/fixtures/accounts.py | 14 ++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a62e6d61..f454c2b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,9 +18,11 @@ ] -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(autouse=True) def fast_mode(): boa.env.enable_fast_mode() + yield + boa.reset_env() def pytest_generate_tests(metafunc): diff --git a/tests/fixtures/accounts.py b/tests/fixtures/accounts.py index bc48f355..e4f2b2f4 100644 --- a/tests/fixtures/accounts.py +++ b/tests/fixtures/accounts.py @@ -194,29 +194,27 @@ def meta_setup( underlying_tokens, pool_tokens, add_initial_liquidity_owner_meta, + metapool_token, ): approve_account(alice, pool_tokens, meta_swap) mint_account(alice, pool_tokens, initial_balance, meta_initial_amounts) mint_for_testing(bob, 1 * 10**18, None, True) - underlying_token = underlying_tokens[0] add_base_pool_liquidity(alice, base_pool, base_pool_tokens, base_pool_decimals) alice_bp_balance_norm = base_pool_lp_token.balanceOf(alice) / 10**18 - alice_mp_balance_norm = underlying_token.balanceOf(alice) / 10 ** underlying_token.decimals() + alice_mp_balance_norm = metapool_token.balanceOf(alice) / 10 ** metapool_token.decimals() if alice_mp_balance_norm < alice_bp_balance_norm: - mint_for_testing( - alice, int(math.ceil(alice_bp_balance_norm) * 10 ** underlying_token.decimals()), underlying_token - ) + mint_for_testing(alice, int(math.ceil(alice_bp_balance_norm) * 10 ** metapool_token.decimals()), metapool_token) with boa.env.prank(alice): - underlying_token.approve(meta_swap.address, 2**256 - 1) + metapool_token.approve(meta_swap.address, 2**256 - 1) base_pool_lp_token.approve(meta_swap.address, 2**256 - 1) meta_swap.add_liquidity(deposit_meta_amounts, 0) add_base_pool_liquidity(bob, base_pool, base_pool_tokens, base_pool_decimals) - mint_for_testing(bob, initial_balance, underlying_token, False) - assert underlying_token.balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) + mint_for_testing(bob, initial_balance, metapool_token, False) + assert metapool_token.balanceOf(bob) == pytest.approx(base_pool_lp_token.balanceOf(bob)) with boa.env.prank(bob): for underlying_token in underlying_tokens: From 3ab3e37d29dbedc58f5d1681a4d62180dccf8e61 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 23 Jan 2024 16:46:52 +0100 Subject: [PATCH 300/337] Try to run with custom vyper version --- README.MD => README.md | 0 contracts/ProxyAdmin.vy | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 2 +- .../main/CurveStableSwapFactoryNGHandler.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 2 +- contracts/main/CurveStableSwapNGMath.vy | 2 +- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/LiquidityGauge.vy | 2 +- contracts/mocks/CallbackSwap.vy | 2 +- contracts/mocks/CurvePool.vy | 2 +- contracts/mocks/CurveTokenV3.vy | 2 +- contracts/mocks/ERC20.vy | 2 +- contracts/mocks/ERC20Oracle.vy | 2 +- contracts/mocks/ERC20Rebasing.vy | 2 +- contracts/mocks/ERC20RebasingConditional.vy | 2 +- poetry.lock | 26 +++++++++++-------- pyproject.toml | 4 +-- .../pools/{general => oracle}/test_oracles.py | 6 +++-- tests/token/test_token_approve.py | 2 +- 20 files changed, 37 insertions(+), 31 deletions(-) rename README.MD => README.md (100%) rename tests/pools/{general => oracle}/test_oracles.py (98%) diff --git a/README.MD b/README.md similarity index 100% rename from README.MD rename to README.md diff --git a/contracts/ProxyAdmin.vy b/contracts/ProxyAdmin.vy index 60c20297..b9674ab2 100644 --- a/contracts/ProxyAdmin.vy +++ b/contracts/ProxyAdmin.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma evm-version paris """ @title ProxyAdmin diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index 23c55dd5..d210cb53 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma evm-version shanghai """ @title CurveStableswapFactoryNG diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index 13250a18..4ded0397 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma evm-version shanghai """ @title CurveStableswapFactoryNGHandler diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 62be99e3..02f0aaa0 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma optimize codesize # pragma evm-version paris """ diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index f192932a..2a384847 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma optimize codesize # pragma evm-version shanghai """ diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 25acc73e..91dbae17 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma optimize gas # pragma evm-version shanghai """ diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 6ff702dd..99370b73 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma evm-version shanghai """ @title CurveStableSwapNGViews diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 8228ecec..334f8f26 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -1,4 +1,4 @@ -# pragma version 0.3.10 +# pragma version 0.1 # pragma optimize gas # pragma evm-version shanghai """ diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index d19c21b4..fa6653f5 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @title CurveExchangeWithoutApproval @author fiddyresearch.eth diff --git a/contracts/mocks/CurvePool.vy b/contracts/mocks/CurvePool.vy index 22a7bde1..5a5642ee 100644 --- a/contracts/mocks/CurvePool.vy +++ b/contracts/mocks/CurvePool.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @title StableSwap @author Curve.Fi diff --git a/contracts/mocks/CurveTokenV3.vy b/contracts/mocks/CurveTokenV3.vy index 0af3dc1b..8bd3c134 100644 --- a/contracts/mocks/CurveTokenV3.vy +++ b/contracts/mocks/CurveTokenV3.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @title Curve LP Token @author Curve.Fi diff --git a/contracts/mocks/ERC20.vy b/contracts/mocks/ERC20.vy index efeee386..fba867f1 100644 --- a/contracts/mocks/ERC20.vy +++ b/contracts/mocks/ERC20.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @notice Mock ERC20 for testing diff --git a/contracts/mocks/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy index 24e62b6e..a459db83 100644 --- a/contracts/mocks/ERC20Oracle.vy +++ b/contracts/mocks/ERC20Oracle.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @notice Mock ERC20 with oracle diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index 232b9178..151c442b 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @notice Rebasing ERC20 mock with rebase by 1% on every transfer diff --git a/contracts/mocks/ERC20RebasingConditional.vy b/contracts/mocks/ERC20RebasingConditional.vy index 77ca22d4..60d7adf4 100644 --- a/contracts/mocks/ERC20RebasingConditional.vy +++ b/contracts/mocks/ERC20RebasingConditional.vy @@ -1,4 +1,4 @@ -# @version ^0.3.9 +# pragma version 0.1 """ @notice Rebasing ERC20 mock with rebase by 1% on every transfer diff --git a/poetry.lock b/poetry.lock index d9d472db..bae021c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2842,7 +2842,7 @@ py-evm = ">=0.7.0a4" pytest = "*" requests = "*" rich = "*" -vyper = ">=0.3.10" +vyper = {git = "https://github.com/DanielSchiavini/vyper.git", rev = "bcf1142d"} [package.extras] forking-recommended = ["ujson"] @@ -2850,8 +2850,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "47e95503b90338ad19114239b53326d3fc850eef" -resolved_reference = "47e95503b90338ad19114239b53326d3fc850eef" +reference = "3fe3585" +resolved_reference = "3fe35853fdd32f430b143833b240687ffeabee12" [[package]] name = "tomli" @@ -2984,14 +2984,12 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "vyper" -version = "0.3.10" +version = "0.1" description = "Vyper: the Pythonic Programming Language for the EVM" optional = false python-versions = ">=3.10,<4" -files = [ - {file = "vyper-0.3.10-py3-none-any.whl", hash = "sha256:05636302341bf89602b19f749fcabc8d184a265d8eea4a45c20b3259780353b0"}, - {file = "vyper-0.3.10.tar.gz", hash = "sha256:8dc1f501caab417fb0ce9c68a6944587f0147ec7cc7d3889cf3a45c19466e489"}, -] +files = [] +develop = false [package.dependencies] asttokens = ">=2.0.5,<3" @@ -3002,11 +3000,17 @@ pycryptodome = ">=3.5.1,<4" wheel = "*" [package.extras] -dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] -docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] +dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx_rtd_theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] +docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx_rtd_theme (>=1.2,<1.3)"] lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] +[package.source] +type = "git" +url = "https://github.com/DanielSchiavini/vyper.git" +reference = "bcf1142d" +resolved_reference = "bcf1142d2ac381b0d50a0a3701b5678a06dffd24" + [[package]] name = "wcwidth" version = "0.2.13" @@ -3162,4 +3166,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d4ae9aec09eff4011767034570acbb0b6d592603ac7bcb27994f1021ef2f2376" +content-hash = "7f01f3b4a81fcf490e8b836cbeefeb9b47c717e2a8e5a2850f5b6e4691913e03" diff --git a/pyproject.toml b/pyproject.toml index 3e516941..f9058774 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "47e95503b90338ad19114239b53326d3fc850eef"} -vyper = "0.3.10" +titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = "3fe3585" } +vyper = { git = "https://github.com/DanielSchiavini/vyper.git", rev = "bcf1142d" } pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/pools/general/test_oracles.py b/tests/pools/oracle/test_oracles.py similarity index 98% rename from tests/pools/general/test_oracles.py rename to tests/pools/oracle/test_oracles.py index 9b74071b..fb3abdea 100644 --- a/tests/pools/general/test_oracles.py +++ b/tests/pools/oracle/test_oracles.py @@ -24,7 +24,9 @@ def get_D(swap, math): def check_oracle(swap, dt): # amm prices: p_amm = [] - for n in range(swap.N_COINS() - 1): + coins = swap.N_COINS() - 1 + assert 0 < coins < 10 + for n in range(coins): _p = swap.get_p(n) assert approx(swap.last_price(n), _p, 1e-5) @@ -39,7 +41,7 @@ def check_oracle(swap, dt): w = exp(-dt / 866) # check: - for n in range(swap.N_COINS() - 1): + for n in range(coins): p1 = int(10**18 * w + p_amm[n] * (1 - w)) assert approx(swap.price_oracle(n), p1, 1e-5) diff --git a/tests/token/test_token_approve.py b/tests/token/test_token_approve.py index 249bc3be..f002cbbe 100644 --- a/tests/token/test_token_approve.py +++ b/tests/token/test_token_approve.py @@ -110,7 +110,7 @@ def test_permit(eth_acc, bob, swap): def test_permit_contract(eth_acc, bob, swap): # based on https://eips.ethereum.org/EIPS/eip-1271 src = """ - # @version ^0.3.9 + # pragma version 0.1 OWNER: public(immutable(address)) @external From 10fa206fc851b1412e1631a017ca1003754e83ec Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 29 Jan 2024 15:04:42 +0100 Subject: [PATCH 301/337] Disable fast mode, clear env contracts --- tests/conftest.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f454c2b9..67fa3b95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,10 +19,21 @@ @pytest.fixture(autouse=True) -def fast_mode(): - boa.env.enable_fast_mode() +def boa_setup(): + import tracemalloc + + tracemalloc.start() + snapshot = tracemalloc.take_snapshot() + # boa.env.enable_fast_mode() yield + boa.env._contracts.clear() + boa.env._code_registry.clear() boa.reset_env() + snapshot2 = tracemalloc.take_snapshot() + top_stats = snapshot2.compare_to(snapshot, "lineno") + print("[ Top 10 differences ]") + for stat in top_stats[:10]: + print(stat) def pytest_generate_tests(metafunc): From c75b89d70f089d937ed20fe5fa62de6ab60dc061 Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:33:28 +0300 Subject: [PATCH 302/337] add release workflow --- .github/workflows/push_pr.yml | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/push_pr.yml diff --git a/.github/workflows/push_pr.yml b/.github/workflows/push_pr.yml new file mode 100644 index 00000000..afa22ae8 --- /dev/null +++ b/.github/workflows/push_pr.yml @@ -0,0 +1,74 @@ +on: + release: + types: [ created ] + +name: on-creating-new-tag + +jobs: + send-pull-requests: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Get production version from tag + id: get_version + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Checkout to repo + uses: actions/checkout@v3 + with: + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Create release file + run: | + TITLE='${{ github.event.release.name }}' + NOTES='${{ github.event.release.body }}' + DATE=$(date '+%d-%m-%Y') + mkdir -p deployments + echo $NOTES >> "deployments/release-$TITLE-$DATE.txt" + + git config user.email "curvefi@users.noreply.github.com" + git config user.name "curvefi" + git remote update + git fetch + git checkout master + git add deployments + git commit -m "chore: add release file - $TITLE" + git push + + - name: Checkout to other repo + uses: actions/checkout@v3 + with: + repository: curvefi/curve-js + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Send pull-request to curve-js + run: | + TAG=${{ env.RELEASE_VERSION }} + ORG="curvefi" + CURRENT_REPOSITORY="$ORG/stableswap-ng" + REPOSITORY="$ORG/curve-js" + FOLDER="bin/$REPOSITORY" + BRANCH_NAME="deployment-$CURRENT_REPOSITORY-$TAG" + + # Setup the committers identity. + git config user.email "Curvefi@users.noreply.github.com" + git config user.name "Curvefi" + + # Create a new feature branch for the changes. + git checkout -b $BRANCH_NAME + + # Store the PAT in a file that can be accessed by the GitHub CLI. + echo "${{ secrets.ACCESS_TOKEN }}" > token.txt + + # Create an empty commit for PR + git commit --allow-empty -m "deployment from $CURRENT_REPOSITORY - $TAG" + git push -u origin $BRANCH_NAME + + # Authorize GitHub CLI for the current repository and + # create a pull-requests containing the updates. + gh auth login --with-token < token.txt + gh pr create \ + --body "Link to release: https://github.com/$CURRENT_REPOSITORY/releases/tag/$TAG" \ + --title "Deployment from $CURRENT_REPOSITORY - $TAG" \ + --head "$BRANCH_NAME" \ + --base "master" From 7c815bc69584d9ca9b05ac913290f1a1d23a7193 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 31 Jan 2024 12:00:34 +0100 Subject: [PATCH 303/337] Fixture fixes --- contracts/mocks/ERC4626.vy | 2 +- contracts/mocks/Zap.vy | 2 +- tests/conftest.py | 12 ++---------- .../test_specific_liquidity_operations.py | 16 ++++++++-------- tests/pools/meta/test_get_virtual_price_meta.py | 10 +++++----- tests/pools/meta/test_meta_new_ng_base.py | 12 ++++++------ 6 files changed, 23 insertions(+), 31 deletions(-) diff --git a/contracts/mocks/ERC4626.vy b/contracts/mocks/ERC4626.vy index 52f2d657..8547d3e9 100644 --- a/contracts/mocks/ERC4626.vy +++ b/contracts/mocks/ERC4626.vy @@ -1,4 +1,4 @@ -# @version 0.3.10 +# pragma version 0.1 # From: https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy from vyper.interfaces import ERC20 from vyper.interfaces import ERC20Detailed diff --git a/contracts/mocks/Zap.vy b/contracts/mocks/Zap.vy index b245916e..67187497 100644 --- a/contracts/mocks/Zap.vy +++ b/contracts/mocks/Zap.vy @@ -1,4 +1,4 @@ -# @version 0.3.10 +# pragma version 0.1 """ @title "Zap" Depositer for permissionless USD metapools @author Curve.Fi diff --git a/tests/conftest.py b/tests/conftest.py index 67fa3b95..f33f5d0f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,20 +20,12 @@ @pytest.fixture(autouse=True) def boa_setup(): - import tracemalloc - - tracemalloc.start() - snapshot = tracemalloc.take_snapshot() - # boa.env.enable_fast_mode() + boa.env.enable_fast_mode() yield + # force reset of the environment to prevent memory leaking between tests boa.env._contracts.clear() boa.env._code_registry.clear() boa.reset_env() - snapshot2 = tracemalloc.take_snapshot() - top_stats = snapshot2.compare_to(snapshot, "lineno") - print("[ Top 10 differences ]") - for stat in top_stats[:10]: - print(stat) def pytest_generate_tests(metafunc): diff --git a/tests/pools/general/test_specific_liquidity_operations.py b/tests/pools/general/test_specific_liquidity_operations.py index 6855d139..15285ff0 100644 --- a/tests/pools/general/test_specific_liquidity_operations.py +++ b/tests/pools/general/test_specific_liquidity_operations.py @@ -5,30 +5,30 @@ from tests.utils.tokens import mint_for_testing -@pytest.fixture(scope="module") +@pytest.fixture() def token_a(deployer, erc20oracle_deployer): with boa.env.prank(deployer): return erc20oracle_deployer.deploy("OTA", "OTA", 18, 1006470359024000000) -@pytest.fixture(scope="module") +@pytest.fixture() def token_b(deployer, erc20oracle_deployer): with boa.env.prank(deployer): return erc20oracle_deployer.deploy("OTB", "OTB", 18, 1000000000000000000) -@pytest.fixture(scope="module") +@pytest.fixture() def token_c(deployer, erc20_deployer): with boa.env.prank(deployer): return erc20_deployer.deploy("OTC", "OTC", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def pool_tokens(token_a, token_b, token_c): return [token_a, token_b, token_c] -@pytest.fixture(scope="module") +@pytest.fixture() def asset_types(pool_tokens): _asset_types = [] for token in pool_tokens: @@ -41,7 +41,7 @@ def asset_types(pool_tokens): return _asset_types -@pytest.fixture(scope="module") +@pytest.fixture() def empty_swap(deployer, factory, pool_tokens, zero_address, amm_deployer, asset_types, set_pool_implementations): pool_size = len(pool_tokens) oracle_method_id = function_signature_to_4byte_selector("exchangeRate()") @@ -74,7 +74,7 @@ def empty_swap(deployer, factory, pool_tokens, zero_address, amm_deployer, asset return amm_deployer.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def deposit_amounts(pool_tokens, bob): _deposit_amounts = [] for i, token in enumerate(pool_tokens): @@ -86,7 +86,7 @@ def deposit_amounts(pool_tokens, bob): return _deposit_amounts -@pytest.fixture(scope="module") +@pytest.fixture() def swap(empty_swap, bob, deposit_amounts, pool_tokens): for token in pool_tokens: token.approve(empty_swap, 2**256 - 1, sender=bob) diff --git a/tests/pools/meta/test_get_virtual_price_meta.py b/tests/pools/meta/test_get_virtual_price_meta.py index 4349c241..7360cc17 100644 --- a/tests/pools/meta/test_get_virtual_price_meta.py +++ b/tests/pools/meta/test_get_virtual_price_meta.py @@ -2,18 +2,18 @@ import pytest -pytestmark = pytest.mark.usefixtures("initial_setup") +pytestmark = pytest.mark.usefixtures("meta_setup") @pytest.mark.parametrize("sending,receiving", itertools.permutations(range(4), 2)) -def test_exchange_underlying(bob, swap, sending, receiving, meta_decimals, base_pool_decimals, underlying_tokens): +def test_exchange_underlying(bob, meta_swap, sending, receiving, meta_decimals, base_pool_decimals, underlying_tokens): underlying_decimals = [meta_decimals] + base_pool_decimals - virtual_price = swap.get_virtual_price() + virtual_price = meta_swap.get_virtual_price() amount = 10 ** underlying_decimals[sending] if sending > 0: underlying_tokens[sending + 1]._mint_for_testing(bob, amount) - swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) + meta_swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) - assert swap.get_virtual_price() > virtual_price + assert meta_swap.get_virtual_price() > virtual_price diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index 242cbb46..cfabc129 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -50,12 +50,12 @@ def ng_base_pool(deployer, factory, ng_base_pool_tokens, zero_address, amm_deplo return amm_deployer.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def ng_metapool_tokens(meta_token, ng_base_pool): return [meta_token, ng_base_pool] -@pytest.fixture(scope="module") +@pytest.fixture() def add_ng_base_pool(owner, factory, ng_base_pool, ng_base_pool_tokens): with boa.env.prank(owner): factory.add_base_pool( @@ -63,7 +63,7 @@ def add_ng_base_pool(owner, factory, ng_base_pool, ng_base_pool_tokens): ) -@pytest.fixture(scope="module") +@pytest.fixture() def empty_swap( deployer, factory, @@ -99,7 +99,7 @@ def empty_swap( return meta_deployer.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, ng_base_pool): for token in [meta_token] + ng_base_pool_tokens: mint_for_testing(bob, 10**25, token) @@ -107,7 +107,7 @@ def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, n token.approve(ng_base_pool, 2**256 - 1, sender=bob) -@pytest.fixture(scope="module") +@pytest.fixture() def deposit_amounts( meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob ): @@ -127,7 +127,7 @@ def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimal return _deposit_amounts -@pytest.fixture(scope="module") +@pytest.fixture() def swap(empty_swap, bob, deposit_amounts): empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) return empty_swap From 6c671f07f5230c8a0286b199ac28967192be1b19 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 31 Jan 2024 12:05:21 +0100 Subject: [PATCH 304/337] Revert to vyper 0.3.10 --- contracts/ProxyAdmin.vy | 2 +- contracts/main/CurveStableSwapFactoryNG.vy | 2 +- .../main/CurveStableSwapFactoryNGHandler.vy | 2 +- contracts/main/CurveStableSwapMetaNG.vy | 2 +- contracts/main/CurveStableSwapNG.vy | 2 +- contracts/main/CurveStableSwapNGMath.vy | 2 +- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/LiquidityGauge.vy | 2 +- contracts/mocks/CallbackSwap.vy | 2 +- contracts/mocks/CurvePool.vy | 2 +- contracts/mocks/CurveTokenV3.vy | 2 +- contracts/mocks/ERC20.vy | 2 +- contracts/mocks/ERC20Oracle.vy | 2 +- contracts/mocks/ERC20Rebasing.vy | 2 +- contracts/mocks/ERC20RebasingConditional.vy | 2 +- contracts/mocks/ERC4626.vy | 2 +- contracts/mocks/Zap.vy | 2 +- poetry.lock | 616 +++++++++--------- pyproject.toml | 4 +- tests/token/test_token_approve.py | 2 +- 20 files changed, 336 insertions(+), 320 deletions(-) diff --git a/contracts/ProxyAdmin.vy b/contracts/ProxyAdmin.vy index b9674ab2..60c20297 100644 --- a/contracts/ProxyAdmin.vy +++ b/contracts/ProxyAdmin.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma evm-version paris """ @title ProxyAdmin diff --git a/contracts/main/CurveStableSwapFactoryNG.vy b/contracts/main/CurveStableSwapFactoryNG.vy index d210cb53..23c55dd5 100644 --- a/contracts/main/CurveStableSwapFactoryNG.vy +++ b/contracts/main/CurveStableSwapFactoryNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma evm-version shanghai """ @title CurveStableswapFactoryNG diff --git a/contracts/main/CurveStableSwapFactoryNGHandler.vy b/contracts/main/CurveStableSwapFactoryNGHandler.vy index 4ded0397..13250a18 100644 --- a/contracts/main/CurveStableSwapFactoryNGHandler.vy +++ b/contracts/main/CurveStableSwapFactoryNGHandler.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma evm-version shanghai """ @title CurveStableswapFactoryNGHandler diff --git a/contracts/main/CurveStableSwapMetaNG.vy b/contracts/main/CurveStableSwapMetaNG.vy index 02f0aaa0..62be99e3 100644 --- a/contracts/main/CurveStableSwapMetaNG.vy +++ b/contracts/main/CurveStableSwapMetaNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma optimize codesize # pragma evm-version paris """ diff --git a/contracts/main/CurveStableSwapNG.vy b/contracts/main/CurveStableSwapNG.vy index 2a384847..f192932a 100644 --- a/contracts/main/CurveStableSwapNG.vy +++ b/contracts/main/CurveStableSwapNG.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma optimize codesize # pragma evm-version shanghai """ diff --git a/contracts/main/CurveStableSwapNGMath.vy b/contracts/main/CurveStableSwapNGMath.vy index 91dbae17..25acc73e 100644 --- a/contracts/main/CurveStableSwapNGMath.vy +++ b/contracts/main/CurveStableSwapNGMath.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma optimize gas # pragma evm-version shanghai """ diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 99370b73..6ff702dd 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma evm-version shanghai """ @title CurveStableSwapNGViews diff --git a/contracts/main/LiquidityGauge.vy b/contracts/main/LiquidityGauge.vy index 334f8f26..8228ecec 100644 --- a/contracts/main/LiquidityGauge.vy +++ b/contracts/main/LiquidityGauge.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # pragma optimize gas # pragma evm-version shanghai """ diff --git a/contracts/mocks/CallbackSwap.vy b/contracts/mocks/CallbackSwap.vy index fa6653f5..96a2e099 100644 --- a/contracts/mocks/CallbackSwap.vy +++ b/contracts/mocks/CallbackSwap.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @title CurveExchangeWithoutApproval @author fiddyresearch.eth diff --git a/contracts/mocks/CurvePool.vy b/contracts/mocks/CurvePool.vy index 5a5642ee..b0424323 100644 --- a/contracts/mocks/CurvePool.vy +++ b/contracts/mocks/CurvePool.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @title StableSwap @author Curve.Fi diff --git a/contracts/mocks/CurveTokenV3.vy b/contracts/mocks/CurveTokenV3.vy index 8bd3c134..a1999cfb 100644 --- a/contracts/mocks/CurveTokenV3.vy +++ b/contracts/mocks/CurveTokenV3.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @title Curve LP Token @author Curve.Fi diff --git a/contracts/mocks/ERC20.vy b/contracts/mocks/ERC20.vy index fba867f1..614781ee 100644 --- a/contracts/mocks/ERC20.vy +++ b/contracts/mocks/ERC20.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @notice Mock ERC20 for testing diff --git a/contracts/mocks/ERC20Oracle.vy b/contracts/mocks/ERC20Oracle.vy index a459db83..69025954 100644 --- a/contracts/mocks/ERC20Oracle.vy +++ b/contracts/mocks/ERC20Oracle.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @notice Mock ERC20 with oracle diff --git a/contracts/mocks/ERC20Rebasing.vy b/contracts/mocks/ERC20Rebasing.vy index 151c442b..14e9579c 100644 --- a/contracts/mocks/ERC20Rebasing.vy +++ b/contracts/mocks/ERC20Rebasing.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @notice Rebasing ERC20 mock with rebase by 1% on every transfer diff --git a/contracts/mocks/ERC20RebasingConditional.vy b/contracts/mocks/ERC20RebasingConditional.vy index 60d7adf4..50ff3042 100644 --- a/contracts/mocks/ERC20RebasingConditional.vy +++ b/contracts/mocks/ERC20RebasingConditional.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @notice Rebasing ERC20 mock with rebase by 1% on every transfer diff --git a/contracts/mocks/ERC4626.vy b/contracts/mocks/ERC4626.vy index 8547d3e9..1e596cdc 100644 --- a/contracts/mocks/ERC4626.vy +++ b/contracts/mocks/ERC4626.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 # From: https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy from vyper.interfaces import ERC20 from vyper.interfaces import ERC20Detailed diff --git a/contracts/mocks/Zap.vy b/contracts/mocks/Zap.vy index 67187497..8fb1670b 100644 --- a/contracts/mocks/Zap.vy +++ b/contracts/mocks/Zap.vy @@ -1,4 +1,4 @@ -# pragma version 0.1 +# pragma version 0.3.10 """ @title "Zap" Depositer for permissionless USD metapools @author Curve.Fi diff --git a/poetry.lock b/poetry.lock index bae021c7..58e38c0f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "asttokens" @@ -269,48 +269,48 @@ files = [ [[package]] name = "cbor2" -version = "5.5.1" +version = "5.6.0" description = "CBOR (de)serializer with extensive tag support" optional = false python-versions = ">=3.8" files = [ - {file = "cbor2-5.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:37ba4f719384bd4ea317e92a8763ea343e205f3112c8241778fd9dbc64ae1498"}, - {file = "cbor2-5.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:425ae919120b9d05b4794b3e5faf6584fc47a9d61db059d4f00ce16ae93a3f63"}, - {file = "cbor2-5.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c511ff6356d6f4292ced856d5048a24ee61a85634816f29dadf1f089e8cb4f9"}, - {file = "cbor2-5.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6ab54a9282dd99a3a70d0f64706d3b3592e7920564a93101caa74dec322346c"}, - {file = "cbor2-5.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:39d94852dd61bda5b3d2bfe74e7b194a7199937d270f90099beec3e7584f0c9b"}, - {file = "cbor2-5.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65532ba929beebe1c63317ad00c79d4936b60a5c29a3c329d2aa7df4e72ad907"}, - {file = "cbor2-5.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:1206180f66a9ad23e692cf457610c877f186ad303a1264b6c5335015b7bee83e"}, - {file = "cbor2-5.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:42155a20be46312fad2ceb85a408e2d90da059c2d36a65e0b99abca57c5357fd"}, - {file = "cbor2-5.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f3827ae14c009df9b37790f1da5cd1f9d64f7ffec472a49ebf865c0af6b77e9"}, - {file = "cbor2-5.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bfa417dbb8b4581ad3c2312469899518596551cfb0fe5bdaf8a6921cff69d7e"}, - {file = "cbor2-5.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3317e7dfb4f3180be90bcd853204558d89f119b624c2168153b53dea305e79d"}, - {file = "cbor2-5.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a5770bdf4340de55679efe6c38fc6d64529fda547e7a85eb0217a82717a8235"}, - {file = "cbor2-5.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b5d53826ad0c92fcb004b2a475896610b51e0ca010f6c37d762aae44ab0807b2"}, - {file = "cbor2-5.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc77cac985f7f7a20f2d8b1957d1e79393d7df823f61c7c6173d3a0011c1d770"}, - {file = "cbor2-5.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9e45d5aa8e484b4bf57240d8e7949389f1c9d4073758abb30954386321b55c9d"}, - {file = "cbor2-5.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93b949a66bec40dd0ca87a6d026136fea2cf1660120f921199a47ac8027af253"}, - {file = "cbor2-5.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93d601ca92d917f769370a5e6c3ead62dca6451b2b603915e4fcf300083b9fcd"}, - {file = "cbor2-5.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11876abd50b9f70d114fcdbb0b5a3249ccd7d321465f0350028fd6d2317e114"}, - {file = "cbor2-5.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fd77c558decdba2a2a7a463e6346d53781d2163bacf205f77b999f561ba4ac73"}, - {file = "cbor2-5.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efb81920d80410b8e80a4a6a8b06ec9b766be0ae7f3029af8ae4b30914edcfa3"}, - {file = "cbor2-5.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:4bb35f3b1ebd4b7b37628f0cd5c839f3008dec669194a2a4a33d91bab7f8663b"}, - {file = "cbor2-5.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f41e4a439f642954ed728dc18915098b5f2ebec7029eaebe52c06c52b6a9a63a"}, - {file = "cbor2-5.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4eae4d56314f22920a28bf7affefdfc918646877ce3b16220dc6cf38a584aa41"}, - {file = "cbor2-5.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559a0c1ec8dcedd6142b81727403e0f5a2e8f4c18e8bb3c548107ec39af4e9cb"}, - {file = "cbor2-5.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537da7bfee97ee44a11b300c034c18e674af6a5dc4718a6fba141037f099c7ec"}, - {file = "cbor2-5.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5c99fd8bbc6bbf3bf4d6b2996594ae633b778b27b0531559487950762c4e1e3f"}, - {file = "cbor2-5.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ee46e6dbc8e2cf302a022fec513d57dba65e9d5ec495bcd1ad97a5dbdbab249"}, - {file = "cbor2-5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:67e2be461320197495fff55f250b111d4125a0a2d02e6256e41f8598adc3ad3f"}, - {file = "cbor2-5.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4384a56afef0b908b61c8ea3cca3e257a316427ace3411308f51ee301b23adf9"}, - {file = "cbor2-5.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8cc64acc606b7f2a4b673a1d6cde5a9cb1860a6ce27b353e269c9535efbd62c"}, - {file = "cbor2-5.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50019fea3cb07fa9b2b53772a52b4243e87de232591570c4c272b3ebdb419493"}, - {file = "cbor2-5.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a18be0af9241883bc67a036c1f33e3f9956d31337ccd412194bf759bc1095e03"}, - {file = "cbor2-5.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:60e7e0073291096605de27de3ce006148cf9a095199160439555f14f93d044d5"}, - {file = "cbor2-5.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41f7501338228b27dac88c1197928cf8985f6fc775f59be89c6fdaddb4e69658"}, - {file = "cbor2-5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c85ab7697252af2240e939707c935ea18081ccb580d4b5b9a94b04148ab2c32b"}, - {file = "cbor2-5.5.1-py3-none-any.whl", hash = "sha256:dca639c8ff81b9f0c92faf97324adfdbfb5c2a5bb97f249606c6f5b94c77cc0d"}, - {file = "cbor2-5.5.1.tar.gz", hash = "sha256:f9e192f461a9f8f6082df28c035b006d153904213dc8640bed8a72d72bbc9475"}, + {file = "cbor2-5.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7569627514699b10d903795e344e5520cd758f7db968e46e667b6875c409610b"}, + {file = "cbor2-5.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e6d5c5b5cb25450561c92c9ac7d72912027cfa8807aab77ea6f5549e2157335"}, + {file = "cbor2-5.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19403d65c32709c4940ae729470dd77ed88ebbd4972355d0ec23ff4dbe34faa3"}, + {file = "cbor2-5.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329196be76fbba0ad63926913eb225dddcfc4891ff6760484fdcf071cebc7188"}, + {file = "cbor2-5.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac835523af0e37086b5f6aac9283e45cffdf6fa0f1be5eecf967014e7184d948"}, + {file = "cbor2-5.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ab6734098c494687b26531ed4d44c06f287a44061ae1bc16b3fa65563d80b0"}, + {file = "cbor2-5.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:777897d46e31bc2683ed9e015a9c97fc97929bf78d620fc3361fe39a86912eba"}, + {file = "cbor2-5.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5d3932c3f0637439f121a54b9e6020e62e9f2620751b2e850a6f09f1c1ee299e"}, + {file = "cbor2-5.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f017d451b9b7e45759e9ffbfae8a2b1bda3f6a547d6451f7761655a8438f956"}, + {file = "cbor2-5.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c66cf65766195c310b2134ceeb20927fce85373d13483e97d2211dd499739f9b"}, + {file = "cbor2-5.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2411ad0fc8817ff38076523bfc43d193188eedcb0d3a1c52428672636f3760d4"}, + {file = "cbor2-5.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b5ce2c7c9172c401db047202029f96a5799c68ff0c936874c596e3718cd383e4"}, + {file = "cbor2-5.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a4e1bb41ac5dc27bd4737c92934e9daf38440ce849eaf3e25da24f9d1fd2195"}, + {file = "cbor2-5.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:83736e076e878f3679ef2dd6cfce5ebcb71f65f9eeada1d14b16b5b87dbc2250"}, + {file = "cbor2-5.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8724f172b581414444801bd7e4974a1512822231e30162c7d5a6374a3c89ddbf"}, + {file = "cbor2-5.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ae83ca762a60b43c83c23edf487e41dc90ba7cc98e25134cde2bf09e99ec1fa3"}, + {file = "cbor2-5.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da8f4aed346ff0ce667254331d341ad27d8d62c8813536f019d8a68aef40eba"}, + {file = "cbor2-5.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99780a5fe8e23a467eb1084feb74b9bdc6598f0eb6c09821b00bb65ddd834d67"}, + {file = "cbor2-5.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0167b4fd537db4925ee8c2890fb950493280628d9c18034625fa5c8a96db689"}, + {file = "cbor2-5.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a073761d9148e4ffbfb0e4125643c059a4d5d5c5c5448cd56759765e1113487"}, + {file = "cbor2-5.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:62ced11efb37729e1a2a5f04fb8c2661d46ae6adbdddc23cb31ffad2ec75ff7a"}, + {file = "cbor2-5.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0130d50aa1cd0ba8ce65eda5bbbb57bda3f2c9cd86bba7d8be5e3c9b19f88f93"}, + {file = "cbor2-5.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:29ba57a33fcffd5d70fd6ac1182520e887918f9d2b6225a06eaae029f070e18f"}, + {file = "cbor2-5.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe7a459aae6ca4abdc490fdf89463cbeea3449569eca853af7c2672286edcea0"}, + {file = "cbor2-5.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0c098a2802ec1df0a6e96c415203c8a52c42158e1e07d8074c9acf19bcc146f"}, + {file = "cbor2-5.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3184d91a855aedb6f0e76e5ac548a1e43a7fcd3d5ba2deb0894b81c77c2c461"}, + {file = "cbor2-5.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:622b4fe945ecdd93df8c21169c8f1a85dcf21d78ecb1e8b7f9f2795520480010"}, + {file = "cbor2-5.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:aaf6a424742f55e2991c7013df02a629d24c64e5730c3fd3aebcbbebc580bfab"}, + {file = "cbor2-5.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e1ff25fb1b6bafbdb192037860471962f59ce9c1f584611572360e15725abbb1"}, + {file = "cbor2-5.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4143152579ff9e0668688d6d41e2f6a20161f8dc5af97b1b92f400d3112888af"}, + {file = "cbor2-5.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7b7cf96e9027138129edbfa0e2024d6f4beb7db42c2a992ab867eff3c04d46"}, + {file = "cbor2-5.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789496ece76712c298f1fd4681d074f6828ed0d788076c5949c4474c11291f68"}, + {file = "cbor2-5.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3d8525d4f525add7971b75b7ff947eed085405d9db7ad6fad456909cbffd18c1"}, + {file = "cbor2-5.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c9423aaf110ce7c3e856f2d5dc6ece7358de48bad7a88d2633f5943d5cd20676"}, + {file = "cbor2-5.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:52fa913133b82578244e6b86b8dff9d88c9d13acf84955b6942650689f302c01"}, + {file = "cbor2-5.6.0-py3-none-any.whl", hash = "sha256:7ba2d0abbb199dd08b7ea59038dbf6524277f124d836b66744cfdd49d31fd012"}, + {file = "cbor2-5.6.0.tar.gz", hash = "sha256:9d94e2226f8f5792fdba5ab20e07b9bfe02e76c10c3ca126418cd4310439d002"}, ] [package.extras] @@ -556,149 +556,169 @@ files = [ [[package]] name = "cryptography" -version = "41.0.7" +version = "42.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, + {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, + {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, + {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, + {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, + {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, + {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "cytoolz" -version = "0.12.2" +version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cytoolz-0.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bff49986c9bae127928a2f9fd6313146a342bfae8292f63e562f872bd01b871"}, - {file = "cytoolz-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:908c13f305d34322e11b796de358edaeea47dd2d115c33ca22909c5e8fb036fd"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:735147aa41b8eeb104da186864b55e2a6623c758000081d19c93d759cd9523e3"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d352d4de060604e605abdc5c8a5d0429d5f156cb9866609065d3003454d4cea"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89247ac220031a4f9f689688bcee42b38fd770d4cce294e5d914afc53b630abe"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070ae35c410d644e6df98a8f69f3ed2807e657d0df2a26b2643127cbf6944a5"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:843500cd3e4884b92fd4037912bc42d5f047108d2c986d36352e880196d465b0"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a93644d7996fd696ab7f1f466cd75d718d0a00d5c8118b9fe8c64231dc1f85e"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96796594c770bc6587376e74ddc7d9c982d68f47116bb69d90873db5e0ea88b6"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:48425107fbb1af3f0f2410c004f16be10ffc9374358e5600b57fa543f46f8def"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:cde6dbb788a4cbc4a80a72aa96386ba4c2b17bdfff3ace0709799adbe16d6476"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68ae7091cc73a752f0b938f15bb193de80ca5edf5ae2ea6360d93d3e9228357b"}, - {file = "cytoolz-0.12.2-cp310-cp310-win32.whl", hash = "sha256:997b7e0960072f6bb445402da162f964ea67387b9f18bda2361edcc026e13597"}, - {file = "cytoolz-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:663911786dcde3e4a5d88215c722c531c7548903dc07d418418c0d1c768072c0"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a7d8b869ded171f6cdf584fc2fc6ae03b30a0e1e37a9daf213a59857a62ed90"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b28787eaf2174e68f0acb3c66f9c6b98bdfeb0930c0d0b08e1941c7aedc8d27"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00547da587f124b32b072ce52dd5e4b37cf199fedcea902e33c67548523e4678"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:275d53fd769df2102d6c9fc98e553bd8a9a38926f54d6b20cf29f0dd00bf3b75"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5556acde785a61d4cf8b8534ae109b023cbd2f9df65ee2afbe070be47c410f8c"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a85b9b9a2530b72b0d3d10e383fc3c2647ae88169d557d5e216f881860318"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673d6e9e3aa86949343b46ac2b7be266c36e07ce77fa1d40f349e6987a814d6e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81e6a9a8fda78a2f4901d2915b25bf620f372997ca1f20a14f7cefef5ad6f6f4"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa44215bc31675a6380cd896dadb7f2054a7b94cfb87e53e52af844c65406a54"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a08b4346350660799d81d4016e748bcb134a9083301d41f9618f64a6077f89f2"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2fb740482794a72e2e5fec58e4d9b00dcd5a60a8cef68431ff12f2ba0e0d9a7e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9007bb1290c79402be6b84bcf9e7a622a073859d61fcee146dc7bc47afe328f3"}, - {file = "cytoolz-0.12.2-cp311-cp311-win32.whl", hash = "sha256:a973f5286758f76824ecf19ae1999f6697371a9121c8f163295d181d19a819d7"}, - {file = "cytoolz-0.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:1ce324d1b413636ea5ee929f79637821f13c9e55e9588f38228947294944d2ed"}, - {file = "cytoolz-0.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c08094b9e5d1b6dfb0845a0253cc2655ca64ce70d15162dfdb102e28c8993493"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf020f4b708f800b353259cd7575e335a79f1ac912d9dda55b2aa0bf3616e42"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4416ee86a87180b6a28e7483102c92debc077bec59c67eda8cc63fc52a218ac0"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ee222671eed5c5b16a5ad2aea07f0a715b8b199ee534834bc1dd2798f1ade7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad92e37be0b106fdbc575a3a669b43b364a5ef334495c9764de4c2d7541f7a99"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460c05238fbfe6d848141669d17a751a46c923f9f0c9fd8a3a462ab737623a44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9e5075e30be626ef0f9bedf7a15f55ed4d7209e832bc314fdc232dbd61dcbf44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:03b58f843f09e73414e82e57f7e8d88f087eaabf8f276b866a40661161da6c51"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e4e612b7ecc9596e7c859cd9e0cd085e6d0c576b4f0d917299595eb56bf9c05"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:08a0e03f287e45eb694998bb55ac1643372199c659affa8319dfbbdec7f7fb3c"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b029bdd5a8b6c9a7c0e8fdbe4fc25ffaa2e09b77f6f3462314696e3a20511829"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win32.whl", hash = "sha256:18580d060fa637ff01541640ecde6de832a248df02b8fb57e6dd578f189d62c7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:97cf514a9f3426228d8daf880f56488330e4b2948a6d183a106921217850d9eb"}, - {file = "cytoolz-0.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18a0f838677f9510aef0330c0096778dd6406d21d4ff9504bf79d85235a18460"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb081b2b02bf4405c804de1ece6f904916838ab0e057f1446e4ac12fac827960"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57233e1600560ceb719bed759dc78393edd541b9a3e7fefc3079abd83c26a6ea"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0295289c4510efa41174850e75bc9188f82b72b1b54d0ea57d1781729c2924d5"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a92aab8dd1d427ac9bc7480cfd3481dbab0ef024558f2f5a47de672d8a5ffaa"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d3495235af09f21aa92a7cdd51504bda640b108b6be834448b774f52852c09"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9c690b359f503f18bf1c46a6456370e4f6f3fc4320b8774ae69c4f85ecc6c94"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:481e3129a76ea01adcc0e7097ccb8dbddab1cfc40b6f0e32c670153512957c0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55e94124af9c8fbb1df54195cc092688fdad0765641b738970b6f1d5ea72e776"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5616d386dfbfba7c39e9418ba668c734f6ceaacc0130877e8a100cad11e6838b"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:732d08228fa8d366fec284f7032cc868d28a99fa81fc71e3adf7ecedbcf33a0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win32.whl", hash = "sha256:f039c5373f7b314b151432c73219216857b19ab9cb834f0eb5d880f74fc7851c"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:246368e983eaee9851b15d7755f82030eab4aa82098d2a34f6bef9c689d33fcc"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81074edf3c74bc9bd250d223408a5df0ff745d1f7a462597536cd26b9390e2d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:960d85ebaa974ecea4e71fa56d098378fa51fd670ee744614cbb95bf95e28fc7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c8d0dff4865da54ae825d43e1721925721b19f3b9aca8e730c2ce73dee2c630"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9d12436fd64937bd2c9609605f527af7f1a8db6e6637639b44121c0fe715d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd461e402e24929d866f05061d2f8337e3a8456e75e21b72c125abff2477c7f7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0568d4da0a9ee9f9f5ab318f6501557f1cfe26d18c96c8e0dac7332ae04c6717"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:101b5bd32badfc8b1f9c7be04ba3ae04fb47f9c8736590666ce9449bff76e0b1"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bb624dbaef4661f5e3625c1e39ad98ecceef281d1380e2774d8084ad0810275"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3e993804e6b04113d61fdb9541b6df2f096ec265a506dad7437517470919c90f"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ab911033e5937fc221a2c165acce7f66ae5ac9d3e54bec56f3c9c197a96be574"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6de6a4bdfaee382c2de2a3580b3ae76fce6105da202bbd835e5efbeae6a9c6e"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9480b4b327be83c4d29cb88bcace761b11f5e30198ffe2287889455c6819e934"}, - {file = "cytoolz-0.12.2-cp38-cp38-win32.whl", hash = "sha256:4180b2785d1278e6abb36a72ac97c92432db53fa2df00ee943d2c15a33627d31"}, - {file = "cytoolz-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:d0086ba8d41d73647b13087a3ca9c020f6bfec338335037e8f5172b4c7c8dce5"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d29988bde28a90a00367edcf92afa1a2f7ecf43ea3ae383291b7da6d380ccc25"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24c0d71e9ac91f4466b1bd280f7de43aa4d94682daaf34d85d867a9b479b87cc"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa436abd4ac9ca71859baf5794614e6ec8fa27362f0162baedcc059048da55f7"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c7b4eac7571707269ebc2893facdf87e359cd5c7cfbfa9e6bd8b33fb1079c5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:294d24edc747ef4e1b28e54365f713becb844e7898113fafbe3e9165dc44aeea"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478051e5ef8278b2429864c8d148efcebdc2be948a61c9a44757cd8c816c98f5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14108cafb140dd68fdda610c2bbc6a37bf052cd48cfebf487ed44145f7a2b67f"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fef7b602ccf8a3c77ab483479ccd7a952a8c5bb1c263156671ba7aaa24d1035"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9bf51354e15520715f068853e6ab8190e77139940e8b8b633bdb587956a08fb0"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:388f840fd911d61a96e9e595eaf003f9dc39e847c9060b8e623ab29e556f009b"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a67f75cc51a2dc7229a8ac84291e4d61dc5abfc8940befcf37a2836d95873340"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63b31345e20afda2ae30dba246955517a4264464d75e071fc2fa641e88c763ec"}, - {file = "cytoolz-0.12.2-cp39-cp39-win32.whl", hash = "sha256:f6e86ac2b45a95f75c6f744147483e0fc9697ce7dfe1726083324c236f873f8b"}, - {file = "cytoolz-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:5998f81bf6a2b28a802521efe14d9fc119f74b64e87b62ad1b0e7c3d8366d0c7"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:593e89e2518eaf81e96edcc9ef2c5fca666e8fc922b03d5cb7a7b8964dbee336"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff451d614ca1d4227db0ffa627fb51df71968cf0d9baf0210528dad10fdbc3ab"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9ea4a50d2948738351790047d45f2b1a023facc01bf0361988109b177e8b2f"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbe038bb78d599b5a29d09c438905defaa615a522bc7e12f8016823179439497"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d494befe648c13c98c0f3d56d05489c839c9228a32f58e9777305deb6c2c1cee"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c26805b6c8dc8565ed91045c44040bf6c0fe5cb5b390c78cd1d9400d08a6cd39"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4e32badb2ccf1773e1e74020b7e3b8caf9e92f842c6be7d14888ecdefc2c6c"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce7889dc3701826d519ede93cdff11940fb5567dbdc165dce0e78047eece02b7"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c820608e7077416f766b148d75e158e454881961881b657cff808529d261dd24"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:698da4fa1f7baeea0607738cb1f9877ed1ba50342b29891b0223221679d6f729"}, - {file = "cytoolz-0.12.2.tar.gz", hash = "sha256:31d4b0455d72d914645f803d917daf4f314d115c70de0578d3820deb8b101f66"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, ] [package.dependencies] @@ -1199,13 +1219,13 @@ lxml = ["lxml"] [[package]] name = "hypothesis" -version = "6.92.9" +version = "6.97.3" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.92.9-py3-none-any.whl", hash = "sha256:8c1ab9f3c883fe63a712bb6c8c1b5be4185cad52775cd7703c040fc0d0111572"}, - {file = "hypothesis-6.92.9.tar.gz", hash = "sha256:629f31788243559d35d3101ef8e94caf736cf8efaad3f0dd66ec7dbb31b8ef19"}, + {file = "hypothesis-6.97.3-py3-none-any.whl", hash = "sha256:6256d768ec866426bfce6ed78418c6e3e43119a0dbece2e0229a1ae5929ae53d"}, + {file = "hypothesis-6.97.3.tar.gz", hash = "sha256:00216ddadaee17ba73451e262f973970a97d34fd75ec34ef57510147264c34d1"}, ] [package.dependencies] @@ -1297,13 +1317,13 @@ files = [ [[package]] name = "ipython" -version = "8.20.0" +version = "8.21.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"}, - {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"}, + {file = "ipython-8.21.0-py3-none-any.whl", hash = "sha256:1050a3ab8473488d7eee163796b02e511d0735cf43a04ba2a8348bd0f2eaf8a5"}, + {file = "ipython-8.21.0.tar.gz", hash = "sha256:48fbc236fbe0e138b88773fa0437751f14c3645fb483f1d4c5dee58b37e5ce73"}, ] [package.dependencies] @@ -1319,17 +1339,17 @@ stack-data = "*" traitlets = ">=5" [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath", "trio"] +test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "isort" @@ -1402,13 +1422,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.20.0" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"}, - {file = "jsonschema-4.20.0.tar.gz", hash = "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -1874,13 +1894,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -2443,13 +2463,13 @@ full = ["numpy"] [[package]] name = "referencing" -version = "0.32.1" +version = "0.33.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, - {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] @@ -2634,110 +2654,110 @@ test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] [[package]] name = "rpds-py" -version = "0.16.2" +version = "0.17.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:509b617ac787cd1149600e731db9274ebbef094503ca25158e6f23edaba1ca8f"}, - {file = "rpds_py-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:413b9c17388bbd0d87a329d8e30c1a4c6e44e2bb25457f43725a8e6fe4161e9e"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2946b120718eba9af2b4dd103affc1164a87b9e9ebff8c3e4c05d7b7a7e274e2"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35ae5ece284cf36464eb160880018cf6088a9ac5ddc72292a6092b6ef3f4da53"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc6a7620ba7639a3db6213da61312cb4aa9ac0ca6e00dc1cbbdc21c2aa6eb57"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cb6fe8ecdfffa0e711a75c931fb39f4ba382b4b3ccedeca43f18693864fe850"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dace7b26a13353e24613417ce2239491b40a6ad44e5776a18eaff7733488b44"}, - {file = "rpds_py-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bdbc5fcb04a7309074de6b67fa9bc4b418ab3fc435fec1f2779a0eced688d04"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f42e25c016927e2a6b1ce748112c3ab134261fc2ddc867e92d02006103e1b1b7"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eab36eae3f3e8e24b05748ec9acc66286662f5d25c52ad70cadab544e034536b"}, - {file = "rpds_py-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0474df4ade9a3b4af96c3d36eb81856cb9462e4c6657d4caecfd840d2a13f3c9"}, - {file = "rpds_py-0.16.2-cp310-none-win32.whl", hash = "sha256:84c5a4d1f9dd7e2d2c44097fb09fffe728629bad31eb56caf97719e55575aa82"}, - {file = "rpds_py-0.16.2-cp310-none-win_amd64.whl", hash = "sha256:2bd82db36cd70b3628c0c57d81d2438e8dd4b7b32a6a9f25f24ab0e657cb6c4e"}, - {file = "rpds_py-0.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:adc0c3d6fc6ae35fee3e4917628983f6ce630d513cbaad575b4517d47e81b4bb"}, - {file = "rpds_py-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec23fcad480e77ede06cf4127a25fc440f7489922e17fc058f426b5256ee0edb"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07aab64e2808c3ebac2a44f67e9dc0543812b715126dfd6fe4264df527556cb6"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4ebb8b20bd09c5ce7884c8f0388801100f5e75e7f733b1b6613c713371feefc"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3d7e2ea25d3517c6d7e5a1cc3702cffa6bd18d9ef8d08d9af6717fc1c700eed"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f28ac0e8e7242d140f99402a903a2c596ab71550272ae9247ad78f9a932b5698"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19f00f57fdd38db4bb5ad09f9ead1b535332dbf624200e9029a45f1f35527ebb"}, - {file = "rpds_py-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3da5a4c56953bdbf6d04447c3410309616c54433146ccdb4a277b9cb499bc10e"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec2e1cf025b2c0f48ec17ff3e642661da7ee332d326f2e6619366ce8e221f018"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e0441fb4fdd39a230477b2ca9be90868af64425bfe7b122b57e61e45737a653b"}, - {file = "rpds_py-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9f0350ef2fba5f34eb0c9000ea328e51b9572b403d2f7f3b19f24085f6f598e8"}, - {file = "rpds_py-0.16.2-cp311-none-win32.whl", hash = "sha256:5a80e2f83391ad0808b4646732af2a7b67550b98f0cae056cb3b40622a83dbb3"}, - {file = "rpds_py-0.16.2-cp311-none-win_amd64.whl", hash = "sha256:e04e56b4ca7a770593633556e8e9e46579d66ec2ada846b401252a2bdcf70a6d"}, - {file = "rpds_py-0.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e6caa3809e50690bd92fa490f5c38caa86082c8c3315aa438bce43786d5e90d"}, - {file = "rpds_py-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e53b9b25cac9065328901713a7e9e3b12e4f57ef4280b370fbbf6fef2052eef"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af27423662f32d7501a00c5e7342f7dbd1e4a718aea7a239781357d15d437133"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d4dd5fb16eb3825742bad8339d454054261ab59fed2fbac84e1d84d5aae7ba"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e061de3b745fe611e23cd7318aec2c8b0e4153939c25c9202a5811ca911fd733"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b811d182ad17ea294f2ec63c0621e7be92a1141e1012383461872cead87468f"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5552f328eaef1a75ff129d4d0c437bf44e43f9436d3996e8eab623ea0f5fcf73"}, - {file = "rpds_py-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dcbe1f8dd179e4d69b70b1f1d9bb6fd1e7e1bdc9c9aad345cdeb332e29d40748"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8aad80645a011abae487d356e0ceb359f4938dfb6f7bcc410027ed7ae4f7bb8b"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6f5549d6ed1da9bfe3631ca9483ae906f21410be2445b73443fa9f017601c6f"}, - {file = "rpds_py-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d452817e0d9c749c431a1121d56a777bd7099b720b3d1c820f1725cb40928f58"}, - {file = "rpds_py-0.16.2-cp312-none-win32.whl", hash = "sha256:888a97002e986eca10d8546e3c8b97da1d47ad8b69726dcfeb3e56348ebb28a3"}, - {file = "rpds_py-0.16.2-cp312-none-win_amd64.whl", hash = "sha256:d8dda2a806dfa4a9b795950c4f5cc56d6d6159f7d68080aedaff3bdc9b5032f5"}, - {file = "rpds_py-0.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:071980663c273bf3d388fe5c794c547e6f35ba3335477072c713a3176bf14a60"}, - {file = "rpds_py-0.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:726ac36e8a3bb8daef2fd482534cabc5e17334052447008405daca7ca04a3108"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9e557db6a177470316c82f023e5d571811c9a4422b5ea084c85da9aa3c035fc"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90123853fc8b1747f80b0d354be3d122b4365a93e50fc3aacc9fb4c2488845d6"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a61f659665a39a4d17d699ab3593d7116d66e1e2e3f03ef3fb8f484e91908808"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc97f0640e91d7776530f06e6836c546c1c752a52de158720c4224c9e8053cad"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a54e99a2b9693a37ebf245937fd6e9228b4cbd64b9cc961e1f3391ec6c7391"}, - {file = "rpds_py-0.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4b677d929cf1f6bac07ad76e0f2d5de367e6373351c01a9c0a39f6b21b4a8b"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5ef00873303d678aaf8b0627e111fd434925ca01c657dbb2641410f1cdaef261"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:349cb40897fd529ca15317c22c0eab67f5ac5178b5bd2c6adc86172045210acc"}, - {file = "rpds_py-0.16.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ddef620e70eaffebed5932ce754d539c0930f676aae6212f8e16cd9743dd365"}, - {file = "rpds_py-0.16.2-cp38-none-win32.whl", hash = "sha256:882ce6e25e585949c3d9f9abd29202367175e0aab3aba0c58c9abbb37d4982ff"}, - {file = "rpds_py-0.16.2-cp38-none-win_amd64.whl", hash = "sha256:f4bd4578e44f26997e9e56c96dedc5f1af43cc9d16c4daa29c771a00b2a26851"}, - {file = "rpds_py-0.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:69ac7ea9897ec201ce68b48582f3eb34a3f9924488a5432a93f177bf76a82a7e"}, - {file = "rpds_py-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a9880b4656efe36ccad41edc66789e191e5ee19a1ea8811e0aed6f69851a82f4"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee94cb58c0ba2c62ee108c2b7c9131b2c66a29e82746e8fa3aa1a1effbd3dcf1"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24f7a2eb3866a9e91f4599851e0c8d39878a470044875c49bd528d2b9b88361c"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca57468da2d9a660bcf8961637c85f2fbb2aa64d9bc3f9484e30c3f9f67b1dd7"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccd4e400309e1f34a5095bf9249d371f0fd60f8a3a5c4a791cad7b99ce1fd38d"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80443fe2f7b3ea3934c5d75fb0e04a5dbb4a8e943e5ff2de0dec059202b70a8b"}, - {file = "rpds_py-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d6a9f052e72d493efd92a77f861e45bab2f6be63e37fa8ecf0c6fd1a58fedb0"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:35953f4f2b3216421af86fd236b7c0c65935936a94ea83ddbd4904ba60757773"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:981d135c7cdaf6cd8eadae1c950de43b976de8f09d8e800feed307140d3d6d00"}, - {file = "rpds_py-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d0dd7ed2f16df2e129496e7fbe59a34bc2d7fc8db443a606644d069eb69cbd45"}, - {file = "rpds_py-0.16.2-cp39-none-win32.whl", hash = "sha256:703d95c75a72e902544fda08e965885525e297578317989fd15a6ce58414b41d"}, - {file = "rpds_py-0.16.2-cp39-none-win_amd64.whl", hash = "sha256:e93ec1b300acf89730cf27975ef574396bc04edecc358e9bd116fb387a123239"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:44627b6ca7308680a70766454db5249105fa6344853af6762eaad4158a2feebe"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3f91df8e6dbb7360e176d1affd5fb0246d2b88d16aa5ebc7db94fd66b68b61da"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d904c5693e08bad240f16d79305edba78276be87061c872a4a15e2c301fa2c0"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:290a81cfbe4673285cdf140ec5cd1658ffbf63ab359f2b352ebe172e7cfa5bf0"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b634c5ec0103c5cbebc24ebac4872b045cccb9456fc59efdcf6fe39775365bd2"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a297a4d08cc67c7466c873c78039d87840fb50d05473db0ec1b7b03d179bf322"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2e75e17bd0bb66ee34a707da677e47c14ee51ccef78ed6a263a4cc965a072a1"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1b9d9260e06ea017feb7172976ab261e011c1dc2f8883c7c274f6b2aabfe01a"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:162d7cd9cd311c1b0ff1c55a024b8f38bd8aad1876b648821da08adc40e95734"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:9b32f742ce5b57201305f19c2ef7a184b52f6f9ba6871cc042c2a61f0d6b49b8"}, - {file = "rpds_py-0.16.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac08472f41ea77cd6a5dae36ae7d4ed3951d6602833af87532b556c1b4601d63"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495a14b72bbe217f2695dcd9b5ab14d4f8066a00f5d209ed94f0aca307f85f6e"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:8d6b6937ae9eac6d6c0ca3c42774d89fa311f55adff3970fb364b34abde6ed3d"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a61226465bda9283686db8f17d02569a98e4b13c637be5a26d44aa1f1e361c2"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5cf6af100ffb5c195beec11ffaa8cf8523057f123afa2944e6571d54da84cdc9"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6df15846ee3fb2e6397fe25d7ca6624af9f89587f3f259d177b556fed6bebe2c"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1be2f033df1b8be8c3167ba3c29d5dca425592ee31e35eac52050623afba5772"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f957d6ab25a78b9e7fc9749d754b98eac825a112b4e666525ce89afcbd9ed5"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:088396c7c70e59872f67462fcac3ecbded5233385797021976a09ebd55961dfe"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4c46ad6356e1561f2a54f08367d1d2e70a0a1bb2db2282d2c1972c1d38eafc3b"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:47713dc4fce213f5c74ca8a1f6a59b622fc1b90868deb8e8e4d993e421b4b39d"}, - {file = "rpds_py-0.16.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f811771019f063bbd0aa7bb72c8a934bc13ebacb4672d712fc1639cfd314cccc"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f19afcfc0dd0dca35694df441e9b0f95bc231b512f51bded3c3d8ca32153ec19"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4b682c5775d6a3d21e314c10124599976809455ee67020e8e72df1769b87bc3"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c647ca87fc0ebe808a41de912e9a1bfef9acb85257e5d63691364ac16b81c1f0"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:302bd4983bbd47063e452c38be66153760112f6d3635c7eeefc094299fa400a9"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf721ede3eb7b829e4a9b8142bd55db0bdc82902720548a703f7e601ee13bdc3"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:358dafc89ce3894c7f486c615ba914609f38277ef67f566abc4c854d23b997fa"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad0f59ee3dc35526039f4bc23642d52d5f6616b5f687d846bfc6d0d6d486db0"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cffa76b385dfe1e38527662a302b19ffb0e7f5cf7dd5e89186d2c94a22dd9d0c"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:83640a5d7cd3bff694747d50436b8b541b5b9b9782b0c8c1688931d6ee1a1f2d"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:ed99b4f7179d2111702020fd7d156e88acd533f5a7d3971353e568b6051d5c97"}, - {file = "rpds_py-0.16.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4022b9dc620e14f30201a8a73898a873c8e910cb642bcd2f3411123bc527f6ac"}, - {file = "rpds_py-0.16.2.tar.gz", hash = "sha256:781ef8bfc091b19960fc0142a23aedadafa826bc32b433fdfe6fd7f964d7ef44"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] [[package]] @@ -2842,7 +2862,7 @@ py-evm = ">=0.7.0a4" pytest = "*" requests = "*" rich = "*" -vyper = {git = "https://github.com/DanielSchiavini/vyper.git", rev = "bcf1142d"} +vyper = ">=0.3.10" [package.extras] forking-recommended = ["ujson"] @@ -2850,8 +2870,8 @@ forking-recommended = ["ujson"] [package.source] type = "git" url = "https://github.com/vyperlang/titanoboa.git" -reference = "3fe3585" -resolved_reference = "3fe35853fdd32f430b143833b240687ffeabee12" +reference = "8343f16bd35829421c0953373aa854fc52b41170" +resolved_reference = "8343f16bd35829421c0953373aa854fc52b41170" [[package]] name = "tomli" @@ -2877,13 +2897,13 @@ files = [ [[package]] name = "toolz" -version = "0.12.0" +version = "0.12.1" description = "List processing tools and functional utilities" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, - {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] [[package]] @@ -2984,12 +3004,14 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "vyper" -version = "0.1" +version = "0.3.10" description = "Vyper: the Pythonic Programming Language for the EVM" optional = false python-versions = ">=3.10,<4" -files = [] -develop = false +files = [ + {file = "vyper-0.3.10-py3-none-any.whl", hash = "sha256:05636302341bf89602b19f749fcabc8d184a265d8eea4a45c20b3259780353b0"}, + {file = "vyper-0.3.10.tar.gz", hash = "sha256:8dc1f501caab417fb0ce9c68a6944587f0147ec7cc7d3889cf3a45c19466e489"}, +] [package.dependencies] asttokens = ">=2.0.5,<3" @@ -3000,17 +3022,11 @@ pycryptodome = ">=3.5.1,<4" wheel = "*" [package.extras] -dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx_rtd_theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] -docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx_rtd_theme (>=1.2,<1.3)"] +dev = ["black (==23.3.0)", "eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "hypothesis[lark] (>=5.37.1,<6.0)", "ipython", "isort (==5.9.3)", "lark (==1.1.2)", "mypy (==0.982)", "pre-commit", "py-evm (>=0.7.0a1,<0.8)", "pyinstaller", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)", "tox (>=3.15,<4.0)", "twine", "web3 (==6.0.0)"] +docs = ["recommonmark", "sphinx (>=6.0,<7.0)", "sphinx-rtd-theme (>=1.2,<1.3)"] lint = ["black (==23.3.0)", "flake8 (==3.9.2)", "flake8-bugbear (==20.1.4)", "flake8-use-fstring (==1.1)", "isort (==5.9.3)", "mypy (==0.982)"] test = ["eth-stdlib (==0.2.6)", "eth-tester[py-evm] (>=0.9.0b1,<0.10)", "hypothesis[lark] (>=5.37.1,<6.0)", "lark (==1.1.2)", "py-evm (>=0.7.0a1,<0.8)", "pytest (>=6.2.5,<7.0)", "pytest-cov (>=2.10,<3.0)", "pytest-instafail (>=0.4,<1.0)", "pytest-rerunfailures (>=10.2,<11)", "pytest-split (>=0.7.0,<1.0)", "pytest-xdist (>=2.5,<3.0)", "tox (>=3.15,<4.0)", "web3 (==6.0.0)"] -[package.source] -type = "git" -url = "https://github.com/DanielSchiavini/vyper.git" -reference = "bcf1142d" -resolved_reference = "bcf1142d2ac381b0d50a0a3701b5678a06dffd24" - [[package]] name = "wcwidth" version = "0.2.13" @@ -3166,4 +3182,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "7f01f3b4a81fcf490e8b836cbeefeb9b47c717e2a8e5a2850f5b6e4691913e03" +content-hash = "d46c8db7b93a0d5ab622f7d07f347e7ff492bf0a9f209355ddffaf8fb2e50f6c" diff --git a/pyproject.toml b/pyproject.toml index f9058774..fe232354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = "3fe3585" } -vyper = { git = "https://github.com/DanielSchiavini/vyper.git", rev = "bcf1142d" } +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "8343f16bd35829421c0953373aa854fc52b41170"} +vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" diff --git a/tests/token/test_token_approve.py b/tests/token/test_token_approve.py index f002cbbe..12dfb96b 100644 --- a/tests/token/test_token_approve.py +++ b/tests/token/test_token_approve.py @@ -110,7 +110,7 @@ def test_permit(eth_acc, bob, swap): def test_permit_contract(eth_acc, bob, swap): # based on https://eips.ethereum.org/EIPS/eip-1271 src = """ - # pragma version 0.1 + # pragma version 0.3.10 OWNER: public(immutable(address)) @external From 3af752e2b1520f02fdebee5c6111433b5d6e654b Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 31 Jan 2024 14:25:34 +0100 Subject: [PATCH 305/337] Convert marker to fixture --- tests/pools/exchange/conftest.py | 16 ++++++++++++++++ tests/pools/exchange/test_exchange_received.py | 18 ++++++++++-------- tests/pools/exchange/test_exchange_reverts.py | 5 +++-- .../test_remove_liquidity_one_coin.py | 8 ++------ tests/pools/meta/test_meta_zap.py | 16 ++++++++-------- 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 tests/pools/exchange/conftest.py diff --git a/tests/pools/exchange/conftest.py b/tests/pools/exchange/conftest.py new file mode 100644 index 00000000..2c8567d0 --- /dev/null +++ b/tests/pools/exchange/conftest.py @@ -0,0 +1,16 @@ +import pytest + +from tests.constants import TOKEN_TYPES +from tests.utils import get_asset_types_in_pool + + +@pytest.fixture() +def contains_rebasing_tokens(swap): + if TOKEN_TYPES["rebasing"] not in get_asset_types_in_pool(swap): + pytest.skip("Test requires pools with no rebasing tokens") + + +@pytest.fixture() +def skip_rebasing_tokens(swap): + if TOKEN_TYPES["rebasing"] in get_asset_types_in_pool(swap): + pytest.skip("Test requires pools with rebasing tokens") diff --git a/tests/pools/exchange/test_exchange_received.py b/tests/pools/exchange/test_exchange_received.py index 1d768b9c..a9f72323 100644 --- a/tests/pools/exchange/test_exchange_received.py +++ b/tests/pools/exchange/test_exchange_received.py @@ -98,9 +98,10 @@ def _transfer_and_swap(pool, sending: int, receiving: int, underlying: bool): return _transfer_and_swap -@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_received_nonrebasing(bob, swap, pool_tokens, sending, receiving, transfer_and_swap): +def test_exchange_received_nonrebasing( + bob, swap, pool_tokens, sending, receiving, transfer_and_swap, skip_rebasing_tokens +): swap_data = transfer_and_swap(swap, sending, receiving, False) assert swap_data["bob"]["sending_token"][0] - swap_data["bob"]["sending_token"][1] == swap_data["amount_in"] @@ -110,16 +111,16 @@ def test_exchange_received_nonrebasing(bob, swap, pool_tokens, sending, receivin assert swap_data["swap"]["receiving_token"][0] - swap_data["swap"]["receiving_token"][1] == swap_data["amount_out"] -@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving): +def test_exchange_not_received(bob, swap, pool_tokens, sending, receiving, skip_rebasing_tokens): with boa.env.prank(bob), boa.reverts(): swap.exchange_received(sending, receiving, 1, 0, bob) -@pytest.mark.skip_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, receiving, transfer_and_swap): +def test_exchange_received_no_dos( + bob, charlie, swap, pool_tokens, sending, receiving, transfer_and_swap, skip_rebasing_tokens +): mint_for_testing(bob, 1, pool_tokens[sending], False) pool_tokens[sending].transfer(swap, 1, sender=bob) @@ -127,9 +128,10 @@ def test_exchange_received_no_dos(bob, charlie, swap, pool_tokens, sending, rece transfer_and_swap(swap, sending, receiving, False) -@pytest.mark.contains_rebasing_tokens @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -def test_exchange_received_rebasing_reverts(bob, swap, transfer_and_swap, pool_tokens, sending, receiving): +def test_exchange_received_rebasing_reverts( + bob, swap, transfer_and_swap, pool_tokens, sending, receiving, contains_rebasing_tokens +): if 2 in get_asset_types_in_pool(swap): with boa.reverts(): transfer_and_swap(swap, sending, receiving, False) diff --git a/tests/pools/exchange/test_exchange_reverts.py b/tests/pools/exchange/test_exchange_reverts.py index 45850ba1..004b874a 100644 --- a/tests/pools/exchange/test_exchange_reverts.py +++ b/tests/pools/exchange/test_exchange_reverts.py @@ -17,8 +17,9 @@ def test_insufficient_balance(charlie, pool_tokens, underlying_tokens, swap, sen @pytest.mark.parametrize("sending,receiving", [(0, 1), (1, 0)]) -@pytest.mark.contains_rebasing_tokens -def test_zero_amount_swap(charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals): +def test_zero_amount_swap( + charlie, pool_tokens, underlying_tokens, swap, sending, receiving, decimals, contains_rebasing_tokens +): with boa.reverts(): swap.exchange(sending, receiving, 0, 0, sender=charlie) diff --git a/tests/pools/liquidity/test_remove_liquidity_one_coin.py b/tests/pools/liquidity/test_remove_liquidity_one_coin.py index 4864e0bd..d5fb565b 100644 --- a/tests/pools/liquidity/test_remove_liquidity_one_coin.py +++ b/tests/pools/liquidity/test_remove_liquidity_one_coin.py @@ -68,11 +68,7 @@ def test_above_n_coins(alice, swap, pool_size): @pytest.mark.parametrize("idx", range(2)) -def test_event(alice, bob, swap, idx, pool_type): +def test_event(alice, bob, swap, idx): swap.transfer(bob, 10**18, sender=alice) _, events = call_returning_result_and_logs(swap, "remove_liquidity_one_coin", 10**18, idx, 0, sender=bob) - - if pool_type == 0: - assert f"RemoveLiquidityOne(provider={bob}" in repr(events[2]) - else: - assert f"RemoveLiquidityOne(provider={bob}" in repr(events[3]) + assert f"RemoveLiquidityOne(provider={bob}" in repr(events[2]) diff --git a/tests/pools/meta/test_meta_zap.py b/tests/pools/meta/test_meta_zap.py index 4d72e194..b713fdf9 100644 --- a/tests/pools/meta/test_meta_zap.py +++ b/tests/pools/meta/test_meta_zap.py @@ -8,23 +8,23 @@ warnings.filterwarnings("ignore") -@pytest.fixture(scope="module") +@pytest.fixture() def meta_token(deployer, erc20_deployer): with boa.env.prank(deployer): return erc20_deployer.deploy("OTA", "OTA", 18) -@pytest.fixture(scope="module") +@pytest.fixture() def metapool_tokens(meta_token, base_pool): return [meta_token, base_pool] -@pytest.fixture(scope="module") +@pytest.fixture() def tokens_all(meta_token, base_pool_tokens): return [meta_token] + base_pool_tokens -@pytest.fixture(scope="module") +@pytest.fixture() def add_base_pool(owner, factory, base_pool, base_pool_lp_token, base_pool_tokens): with boa.env.prank(owner): factory.add_base_pool( @@ -32,7 +32,7 @@ def add_base_pool(owner, factory, base_pool, base_pool_lp_token, base_pool_token ) -@pytest.fixture(scope="module") +@pytest.fixture() def empty_swap( deployer, factory, zero_address, meta_token, base_pool, meta_deployer, add_base_pool, set_metapool_implementations ): @@ -62,17 +62,17 @@ def empty_swap( return meta_deployer.at(pool) -@pytest.fixture(scope="module") +@pytest.fixture() def zap(base_pool, base_pool_tokens, base_pool_lp_token, zap_deployer): return zap_deployer.deploy(base_pool.address, base_pool_lp_token.address, [a.address for a in base_pool_tokens]) -@pytest.fixture(scope="module") +@pytest.fixture() def initial_amts(): return [100 * 10**18] * 4 -@pytest.fixture(scope="module") +@pytest.fixture() def swap(zap, base_pool, empty_swap, charlie, tokens_all, initial_amts): for i in range(3): assert base_pool.balances(i) == 0 From ddbbc9a8c9b098cb5b1d7c2d0fd08ee65adfb19a Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 31 Jan 2024 14:26:20 +0100 Subject: [PATCH 306/337] No init for test directories --- tests/factory/__init__.py | 0 tests/gauge/__init__.py | 0 tests/pools/__init__.py | 0 tests/pools/exchange/__init__.py | 0 tests/pools/general/__init__.py | 0 tests/pools/liquidity/__init__.py | 0 tests/pools/meta/__init__.py | 0 tests/pools/oracle/__init__.py | 0 tests/token/__init__.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/factory/__init__.py delete mode 100644 tests/gauge/__init__.py delete mode 100644 tests/pools/__init__.py delete mode 100644 tests/pools/exchange/__init__.py delete mode 100644 tests/pools/general/__init__.py delete mode 100644 tests/pools/liquidity/__init__.py delete mode 100644 tests/pools/meta/__init__.py delete mode 100644 tests/pools/oracle/__init__.py delete mode 100644 tests/token/__init__.py diff --git a/tests/factory/__init__.py b/tests/factory/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/gauge/__init__.py b/tests/gauge/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/__init__.py b/tests/pools/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/exchange/__init__.py b/tests/pools/exchange/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/general/__init__.py b/tests/pools/general/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/liquidity/__init__.py b/tests/pools/liquidity/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/meta/__init__.py b/tests/pools/meta/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/pools/oracle/__init__.py b/tests/pools/oracle/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/token/__init__.py b/tests/token/__init__.py deleted file mode 100644 index e69de29b..00000000 From 60a81f1eec3c390d86d49e51755994eb0a303d6d Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 2 Feb 2024 13:58:12 +0100 Subject: [PATCH 307/337] Document markers --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fe232354..6214d430 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,10 @@ py_version = 310 known_first_party = "poetry" [tool.pytest.ini_options] +markers = [ + "only_plain_tokens", "only_oracle_tokens", "only_rebasing_tokens", + "skip_plain_tokens", "skip_oracle_tokens", "skip_rebasing_tokens", +] filterwarnings = [ "ignore:PytestUnknownMarkWarning" ] From 42fec1b94bffb32a4a65821a053083d79bd1b0f6 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 2 Feb 2024 13:58:34 +0100 Subject: [PATCH 308/337] Revert script formatting --- scripts/deploy_infra.py | 13 +++++- scripts/deploy_pool.py | 76 ++++++++++++++++++++++++++--------- scripts/deploy_proxy_admin.py | 8 +++- scripts/deployment_utils.py | 8 +++- scripts/set_up_base_pools.py | 54 ++++++++++++++++++++----- scripts/vote_utils.py | 1 + 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 8026a51e..be0c694c 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -139,6 +139,7 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: + with open(contract_file, "r") as f: source = f.read() @@ -158,6 +159,7 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): + deployed_contract = deployments[network][contract_designation] if not deployed_contract: @@ -178,6 +180,7 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo def deploy_infra(network, url, account, fork=False): + logger.log(f"Deploying on {network} ...") if fork: @@ -189,7 +192,9 @@ def deploy_infra(network, url, account, fork=False): boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) for _network, data in deploy_utils.curve_dao_network_settings.items(): + if _network in network: + owner = data.dao_ownership_contract fee_receiver = data.fee_receiver_address @@ -247,7 +252,13 @@ def deploy_infra(network, url, account, fork=False): def main(): - deploy_infra("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False) + + deploy_infra( + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], + "FIDDYDEPLOYER", + fork=False, + ) if __name__ == "__main__": diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 3965f70c..885d6946 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -15,27 +15,63 @@ deployments = { # Ethereum - "ethereum:sepolia": {"factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81"}, - "ethereum:mainnet": {"factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf"}, + "ethereum:sepolia": { + "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", + }, + "ethereum:mainnet": { + "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", + }, # Layer 2 - "arbitrum:mainnet": {"factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b"}, - "optimism:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, - "base:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, - "linea:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, - "scroll:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, - "zksync:mainnet": {"factory": ""}, - "pzkevm:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, + "arbitrum:mainnet": { + "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", + }, + "optimism:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "base:mainnet": { + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + }, + "linea:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "scroll:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "zksync:mainnet": { + "factory": "", + }, + "pzkevm:mainnet": { + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + }, "mantle:mainnet": {"factory": ""}, # Layer 1 - "gnosis:mainnet": {"factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8"}, - "polygon:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, - "avax:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, - "ftm:mainnet": {"factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b"}, - "bsc:mainnet": {"factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B"}, - "celo:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, - "kava:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, - "aurora:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, - "tron:mainnet": {"factory": ""}, + "gnosis:mainnet": { + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + }, + "polygon:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "avax:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "ftm:mainnet": { + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, + "bsc:mainnet": { + "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + }, + "celo:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "kava:mainnet": { + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + }, + "aurora:mainnet": { + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + }, + "tron:mainnet": { + "factory": "", + }, } @@ -94,6 +130,7 @@ class PoolSettings: def deploy_pool(network, url, account, pool_type, fork): + logger.log(f"Deploying pool on {network} ...") if fork: @@ -121,6 +158,7 @@ def deploy_pool(network, url, account, pool_type, fork): def deploy_gauge(network, url, account, pool_addr, fork): + logger.log(f"Deploying gauge for pool {pool_addr} on {network} ...") if fork: @@ -143,6 +181,7 @@ def deploy_gauge(network, url, account, pool_addr, fork): def deploy_pool_and_gauge(network, url, account, pool_type, fork): + logger.log(f"Deploying pool on {network} ...") if fork: @@ -172,6 +211,7 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): + fork = False deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090/", "FIDDYDEPLOYER", "meta", fork) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index fca78790..ca607c85 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -13,6 +13,7 @@ def deploy_proxy_admin(network, url, account, fork=False): + logger.log(f"Deploying ProxyAdmin for {network} ...") if fork: @@ -34,7 +35,12 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): - deploy_proxy_admin(":mainnet", os.environ["RPC_"], "", fork=False) + deploy_proxy_admin( + ":mainnet", + os.environ["RPC_"], + "", + fork=False, + ) if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 84ff7a58..b7af3632 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -58,7 +58,8 @@ class CurveNetworkSettings: fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "zksync:mainnet": CurveNetworkSettings( - dao_ownership_contract="", fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1" + dao_ownership_contract="", + fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1", ), "pzkevm:mainnet": CurveNetworkSettings( dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", @@ -97,7 +98,10 @@ class CurveNetworkSettings: dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), - "tron:mainnet": CurveNetworkSettings(dao_ownership_contract="", fee_receiver_address=""), + "tron:mainnet": CurveNetworkSettings( + dao_ownership_contract="", + fee_receiver_address="", + ), "mantle:mainnet": CurveNetworkSettings( dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", diff --git a/scripts/set_up_base_pools.py b/scripts/set_up_base_pools.py index bcb57086..06d127ed 100644 --- a/scripts/set_up_base_pools.py +++ b/scripts/set_up_base_pools.py @@ -75,14 +75,20 @@ class BasePoolSettings: BasePoolSettings( # 2pool pool="0x7f90122BF0700F9E7e1F688fe926940E8839F353", lp_token="0x7f90122BF0700F9E7e1F688fe926940E8839F353", - coins=["0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"], + coins=[ + "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + ], asset_types=[0, 0], n_coins=2, ), BasePoolSettings( # fraxbp pool="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", lp_token="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", - coins=["0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"], + coins=[ + "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + ], asset_types=[0, 0], n_coins=2, ), @@ -102,7 +108,10 @@ class BasePoolSettings: BasePoolSettings( # fraxbp pool="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", lp_token="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", - coins=["0x2E3D870790dC77A83DD1d18184Acc7439A53f475", "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"], + coins=[ + "0x2E3D870790dC77A83DD1d18184Acc7439A53f475", + "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + ], asset_types=[0, 0], n_coins=2, ), @@ -118,12 +127,13 @@ class BasePoolSettings: ], asset_types=[0, 0, 0], n_coins=3, - ) + ), ], } def set_up_base_pools(network, url, account, fork: bool = False): + logger.log(f"Connecting to {network} ...") if fork: boa.env.fork(url) @@ -144,7 +154,12 @@ def set_up_base_pools(network, url, account, fork: bool = False): if base_pool_data: # check if network has base pools: for data in base_pool_data: if to_checksum_address(data.pool) not in onboarded_base_pools: - factory.add_base_pool(data.pool, data.lp_token, data.asset_types, data.n_coins) + factory.add_base_pool( + data.pool, + data.lp_token, + data.asset_types, + data.n_coins, + ) logger.log(f"Added {data.pool} to factory {factory_address} on {network}.") else: logger.log(f"{data.pool} is already configured as a base pool in factory {factory_address}.") @@ -155,12 +170,33 @@ def set_up_base_pools(network, url, account, fork: bool = False): def main(): + fork = False - set_up_base_pools("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=fork) - set_up_base_pools("arbitrum:mainnet", os.environ["RPC_ARBITRUM"], "FIDDYDEPLOYER", fork=fork) - set_up_base_pools("optimism:mainnet", os.environ["RPC_OPTIMISM"], "FIDDYDEPLOYER", fork=fork) - set_up_base_pools("gnosis:mainnet", os.environ["RPC_GNOSIS"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools( + "ethereum:mainnet", + os.environ["RPC_ETHEREUM"], + "FIDDYDEPLOYER", + fork=fork, + ) + set_up_base_pools( + "arbitrum:mainnet", + os.environ["RPC_ARBITRUM"], + "FIDDYDEPLOYER", + fork=fork, + ) + set_up_base_pools( + "optimism:mainnet", + os.environ["RPC_OPTIMISM"], + "FIDDYDEPLOYER", + fork=fork, + ) + set_up_base_pools( + "gnosis:mainnet", + os.environ["RPC_GNOSIS"], + "FIDDYDEPLOYER", + fork=fork, + ) if __name__ == "__main__": diff --git a/scripts/vote_utils.py b/scripts/vote_utils.py index b9043e9a..fd7defe7 100644 --- a/scripts/vote_utils.py +++ b/scripts/vote_utils.py @@ -47,6 +47,7 @@ def prepare_evm_script(target: Dict, actions: List[Tuple]) -> str: evm_script = "0x00000001" for address, fn_name, *args in actions: + contract = ape.Contract(address) fn = getattr(contract, fn_name) calldata = fn.as_transaction(*args, sender=agent.address, gas_price=0).data From f507773017ff0320d0df4cdb17d0b28cafeef5a2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:06:34 +0100 Subject: [PATCH 309/337] add meta ng zap --- contracts/main/MetaZapNG.vy | 439 ++++++++++++++++++++++ scripts/deploy_infra.py | 13 +- scripts/deploy_pool.py | 76 +--- scripts/deploy_proxy_admin.py | 8 +- scripts/deployment_utils.py | 8 +- scripts/set_up_base_pools.py | 54 +-- scripts/vote_utils.py | 1 - tests/fixtures/contracts.py | 5 + tests/pools/meta/test_meta_zap_ng_base.py | 205 ++++++++++ 9 files changed, 680 insertions(+), 129 deletions(-) create mode 100644 contracts/main/MetaZapNG.vy create mode 100644 tests/pools/meta/test_meta_zap_ng_base.py diff --git a/contracts/main/MetaZapNG.vy b/contracts/main/MetaZapNG.vy new file mode 100644 index 00000000..f3202b78 --- /dev/null +++ b/contracts/main/MetaZapNG.vy @@ -0,0 +1,439 @@ +# pragma version 0.3.10 +""" +@title MetaZapNG +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2021 - all rights reserved +@notice A generalised zap contract for Stableswap-ng metapools where the base pool + is a Stableswap-ng implementation as well. +@dev Contract assumes Metapools have 2 coins. +""" + +interface ERC20: + def transfer(receiver: address, amount: uint256): nonpayable + def transferFrom(_sender: address, receiver: address, amount: uint256): nonpayable + def approve(spender: address, amount: uint256): nonpayable + def decimals() -> uint256: view + def balanceOf(owner: address) -> uint256: view + +interface StableSwapMetaNG: + def add_liquidity( + amounts: uint256[META_N_COINS], + min_mint_amount: uint256, + receiver: address + ) -> uint256: nonpayable + def remove_liquidity( + amount: uint256, + min_amounts: uint256[META_N_COINS] + ) -> uint256[META_N_COINS]: nonpayable + def remove_liquidity_one_coin( + token_amount: uint256, + i: int128, + min_amount: uint256, + receiver: address + ) -> uint256: nonpayable + def remove_liquidity_imbalance( + amounts: uint256[META_N_COINS], + max_burn_amount: uint256 + ) -> uint256: nonpayable + def calc_withdraw_one_coin(token_amount: uint256, i: int128) -> uint256: view + def calc_token_amount(amounts: uint256[META_N_COINS], deposit: bool) -> uint256: view + def coins(i: uint256) -> address: view + def BASE_POOL() -> address: view + def BASE_POOL_IS_NG() -> bool: view + +interface StableSwapNG: + def N_COINS() -> uint256: view + def add_liquidity( + amounts: DynArray[uint256, MAX_COINS], + min_mint_amount: uint256 + ) -> uint256: nonpayable + def remove_liquidity( + amount: uint256, + min_amounts: DynArray[uint256, MAX_COINS] + ) -> DynArray[uint256, MAX_COINS]: nonpayable + def remove_liquidity_one_coin( + token_amount: uint256, + i: int128, + min_amount: uint256 + ) -> uint256: nonpayable + def remove_liquidity_imbalance( + amounts: DynArray[uint256, MAX_COINS], + max_burn_amount: uint256 + ) -> uint256: nonpayable + def calc_withdraw_one_coin(token_amount: uint256, i: int128) -> uint256: view + def calc_token_amount( + amounts: DynArray[uint256, MAX_COINS], + deposit: bool + ) -> uint256: view + def coins(i: uint256) -> address: view + def fee() -> uint256: view + + +struct BasePool: + pool_address: address + coins: DynArray[address, MAX_COINS] + + +META_N_COINS: constant(uint256) = 2 +MAX_COINS: constant(uint256) = 8 +MAX_ALL_COINS: constant(uint256) = MAX_COINS + 1 +FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +FEE_IMPRECISION: constant(uint256) = 100 * 10 ** 8 # % of the fee + +# coin -> pool -> is approved to transfer? +is_approved: HashMap[address, HashMap[address, bool]] +base_pool_coins_spending_approved: HashMap[address, bool] +base_pool_registry: HashMap[address, BasePool] + + +@internal +@view +def get_coins_from_pool(_pool: address) -> DynArray[address, MAX_COINS]: + n_coins: uint256 = StableSwapNG(_pool).N_COINS() + coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + for i in range(n_coins, bound=MAX_COINS): + coins.append(StableSwapNG(_pool).coins(i)) + return coins + + +@internal +def _approve_pool_to_spend_zap_coins( + pool: address, + coins: DynArray[address, MAX_COINS], +): + for i in range(len(coins), bound=MAX_COINS): + ERC20(coins[i]).approve(pool, max_value(uint256)) + + self.base_pool_coins_spending_approved[pool] = True + + +@internal +@view +def _fetch_base_pool_data(_pool: address) -> (address, DynArray[address, MAX_COINS]): + + base_pool: address = StableSwapMetaNG(_pool).BASE_POOL() + assert base_pool != empty(address) # dev: not a metapool + base_coins: DynArray[address, MAX_COINS] = self.get_coins_from_pool(base_pool) + return base_pool, base_coins + + +@internal +def _base_pool_data(_pool: address) -> (address, DynArray[address, MAX_COINS]): + + base_pool_data: BasePool = self.base_pool_registry[_pool] + if base_pool_data.pool_address == empty(address): + + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._fetch_base_pool_data(_pool) + + self.base_pool_registry[_pool] = BasePool( + {pool_address: base_pool, coins: base_coins} + ) + return base_pool, base_coins + + return base_pool_data.pool_address, base_pool_data.coins + + + +@view +@external +def calc_token_amount( + _pool: address, + _amounts: DynArray[uint256, MAX_ALL_COINS], + _is_deposit: bool +) -> uint256: + """ + @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @dev This calculation accounts for slippage, but not fees. + Needed to prevent front-running, not for precise calculations! + @param _pool Address of the pool to deposit into + @param _amounts Amount of each underlying coin being deposited + @param _is_deposit set True for deposits, False for withdrawals + @return Expected amount of LP tokens received + """ + meta_amounts: uint256[META_N_COINS] = empty(uint256[META_N_COINS]) + base_amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._fetch_base_pool_data(_pool) + base_n_coins: uint256 = len(base_coins) + + meta_amounts[0] = _amounts[0] + for i in range(base_n_coins, bound=MAX_COINS): + base_amounts.append(_amounts[i + META_N_COINS - 1]) + + base_tokens: uint256 = StableSwapNG(base_pool).calc_token_amount(base_amounts, _is_deposit) + meta_amounts[META_N_COINS - 1] = base_tokens + + return StableSwapMetaNG(_pool).calc_token_amount(meta_amounts, _is_deposit) + + +@external +def add_liquidity( + _pool: address, + _deposit_amounts: DynArray[uint256, MAX_ALL_COINS], + _min_mint_amount: uint256, + _receiver: address = msg.sender, +) -> uint256: + """ + @notice Wrap underlying coins and deposit them into `_pool` + @param _pool Address of the pool to deposit into + @param _deposit_amounts List of amounts of underlying coins to deposit + @param _min_mint_amount Minimum amount of LP tokens to mint from the deposit + @param _receiver Address that receives the LP tokens + @return Amount of LP tokens received by depositing + """ + + base_amounts: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + deposit_base: bool = False + + # -------------------------- Get base pool data -------------------------- + + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._base_pool_data(_pool) + n_all_coins: uint256 = len(base_coins) + 1 + + if not self.base_pool_coins_spending_approved[base_pool]: + self._approve_pool_to_spend_zap_coins(base_pool, base_coins) + + # ------------------------ Transfer tokens to Zap ------------------------ + + meta_amounts: uint256[META_N_COINS] = empty(uint256[META_N_COINS]) + + # Transfer meta-token (token in metapool that is not base pool token) if + # any: + if _deposit_amounts[0] != 0: + coin: address = StableSwapMetaNG(_pool).coins(0) + if not self.is_approved[coin][_pool]: + ERC20(coin).approve(_pool, max_value(uint256)) + self.is_approved[coin][_pool] = True + ERC20(coin).transferFrom(msg.sender, self, _deposit_amounts[0]) + meta_amounts[0] = _deposit_amounts[0] + + # Transfer base pool coins (if any): + for i in range(n_all_coins, bound=MAX_ALL_COINS): + + amount: uint256 = _deposit_amounts[i] + base_amounts.append(0) + if i == 0 or amount == 0: + base_amounts.append(0) + continue + + deposit_base = True + base_idx: uint256 = i - 1 + coin: address = base_coins[base_idx] + + ERC20(coin).transferFrom(msg.sender, self, amount) + base_amounts[base_idx] = amount + + # ----------------------- Deposit to the base pool ----------------------- + + if deposit_base: + meta_amounts[META_N_COINS - 1] = StableSwapNG(base_pool).add_liquidity(base_amounts, 0) + if not self.is_approved[base_pool][_pool]: + ERC20(base_pool).approve(_pool, max_value(uint256)) + self.is_approved[base_pool][_pool] = True + + # ----------------------- Deposit to the meta pool ----------------------- + + return StableSwapMetaNG(_pool).add_liquidity( + meta_amounts, + _min_mint_amount, + _receiver + ) + + +@view +@external +def calc_withdraw_one_coin(_pool: address, _token_amount: uint256, i: int128) -> uint256: + """ + @notice Calculate the amount received when withdrawing and unwrapping a single coin + @param _pool Address of the pool to deposit into + @param _token_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the underlying coin to withdraw + @return Amount of coin received + """ + if i < META_N_COINS - 1: + return StableSwapMetaNG(_pool).calc_withdraw_one_coin(_token_amount, i) + else: + base_pool: address = StableSwapMetaNG(_pool).BASE_POOL() + assert base_pool != empty(address) # dev: not a metapool! + _base_tokens: uint256 = StableSwapMetaNG(_pool).calc_withdraw_one_coin(_token_amount, META_N_COINS - 1) + return StableSwapNG(base_pool).calc_withdraw_one_coin( + _base_tokens, + i - convert(META_N_COINS - 1, int128) + ) + + +@external +def remove_liquidity( + _pool: address, + _burn_amount: uint256, + _min_amounts: DynArray[uint256, MAX_ALL_COINS], + _receiver: address = msg.sender +) -> DynArray[uint256, MAX_ALL_COINS]: + """ + @notice Withdraw and unwrap coins from the pool + @dev Withdrawal amounts are based on current deposit ratios + @param _pool Address of the pool to deposit into + @param _burn_amount Quantity of LP tokens to burn in the withdrawal + @param _min_amounts Minimum amounts of underlying coins to receive + @param _receiver Address that receives the LP tokens + @return List of amounts of underlying coins that were withdrawn + """ + ERC20(_pool).transferFrom(msg.sender, self, _burn_amount) + + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._base_pool_data(_pool) + base_n_coins: uint256 = len(base_coins) + + min_amounts_base: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + amounts: DynArray[uint256, MAX_ALL_COINS] = empty(DynArray[uint256, MAX_ALL_COINS]) + + # Withdraw from meta + meta_received: uint256[META_N_COINS] = StableSwapMetaNG(_pool).remove_liquidity( + _burn_amount, + [_min_amounts[0], convert(0, uint256)] + ) + + # Withdraw from base + for i in range(base_n_coins, bound=MAX_COINS): + min_amounts_base.append(_min_amounts[i + META_N_COINS - 1]) + StableSwapNG(base_pool).remove_liquidity(meta_received[1], min_amounts_base) + + # Transfer all coins out + coin: address = StableSwapMetaNG(_pool).coins(0) + ERC20(coin).transfer(_receiver, meta_received[0]) + amounts.append(meta_received[0]) + + for i in range(base_n_coins + 1, bound=MAX_ALL_COINS): + + if i == 0: + continue + + coin = base_coins[i-1] + amounts.append(ERC20(coin).balanceOf(self)) + + ERC20(coin).transfer(_receiver, amounts[i]) + + return amounts + + +@external +def remove_liquidity_one_coin( + _pool: address, + _burn_amount: uint256, + i: int128, + _min_amount: uint256, + _receiver: address=msg.sender +) -> uint256: + """ + @notice Withdraw and unwrap a single coin from the pool + @param _pool Address of the pool to deposit into + @param _burn_amount Amount of LP tokens to burn in the withdrawal + @param i Index value of the coin to withdraw + @param _min_amount Minimum amount of underlying coin to receive + @param _receiver Address that receives the LP tokens + @return Amount of underlying coin received + """ + ERC20(_pool).transferFrom(msg.sender, self, _burn_amount) + + coin_amount: uint256 = 0 + if i == 0: + coin_amount = StableSwapMetaNG(_pool).remove_liquidity_one_coin( + _burn_amount, i, _min_amount, _receiver + ) + else: + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._base_pool_data(_pool) + base_n_coins: uint256 = len(base_coins) + + coin: address = base_coins[i - convert(META_N_COINS - 1, int128)] + # Withdraw a base pool coin + coin_amount = StableSwapMetaNG(_pool).remove_liquidity_one_coin( + _burn_amount, convert(META_N_COINS - 1, int128), 0, self + ) + coin_amount = StableSwapNG(base_pool).remove_liquidity_one_coin( + coin_amount, i - convert(META_N_COINS - 1, int128), _min_amount + ) + ERC20(coin).transfer(_receiver, coin_amount) + + return coin_amount + + +@external +def remove_liquidity_imbalance( + _pool: address, + _amounts: DynArray[uint256, MAX_ALL_COINS], + _max_burn_amount: uint256, + _receiver: address=msg.sender +) -> uint256: + """ + @notice Withdraw coins from the pool in an imbalanced amount + @param _pool Address of the pool to deposit into + @param _amounts List of amounts of underlying coins to withdraw + @param _max_burn_amount Maximum amount of LP token to burn in the withdrawal + @param _receiver Address that receives the LP tokens + @return Actual amount of the LP token burned in the withdrawal + """ + + base_pool: address = empty(address) + base_coins: DynArray[address, MAX_COINS] = empty(DynArray[address, MAX_COINS]) + base_pool, base_coins = self._base_pool_data(_pool) + base_n_coins: uint256 = len(base_coins) + + fee: uint256 = StableSwapNG(base_pool).fee() * base_n_coins / (4 * (base_n_coins - 1)) + fee += fee * FEE_IMPRECISION / FEE_DENOMINATOR # Overcharge to account for imprecision + + # Transfer the LP token in + ERC20(_pool).transferFrom(msg.sender, self, _max_burn_amount) + + withdraw_base: bool = False + amounts_base: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + amounts_meta: uint256[META_N_COINS] = [_amounts[0], 0] + + # determine amounts to withdraw from base pool + for i in range(base_n_coins, bound=MAX_COINS): + amount: uint256 = _amounts[META_N_COINS - 1 + i] + if amount != 0: + amounts_base.append(amount) + withdraw_base = True + else: + amounts_base.append(0) + + # determine amounts to withdraw from metapool + if withdraw_base: + amounts_meta[1] = StableSwapNG(base_pool).calc_token_amount(amounts_base, False) + amounts_meta[1] += amounts_meta[1] * fee / FEE_DENOMINATOR + 1 + + # withdraw from metapool and return the remaining LP tokens + burn_amount: uint256 = StableSwapMetaNG(_pool).remove_liquidity_imbalance(amounts_meta, _max_burn_amount) + ERC20(_pool).transfer(msg.sender, _max_burn_amount - burn_amount) + + # withdraw from base pool + if withdraw_base: + StableSwapNG(base_pool).remove_liquidity_imbalance(amounts_base, amounts_meta[1]) + coin: address = base_pool + leftover: uint256 = ERC20(coin).balanceOf(self) + + if leftover > 0: + # if some base pool LP tokens remain, re-deposit them for the caller + if not self.is_approved[coin][_pool]: + ERC20(coin).approve(_pool, MAX_UINT256) + self.is_approved[coin][_pool] = True + burn_amount -= StableSwapMetaNG(_pool).add_liquidity([convert(0, uint256), leftover], 0, msg.sender) + + # transfer withdrawn base pool tokens to caller + for i in range(base_n_coins, bound=MAX_COINS): + ERC20(base_coins[i]).transfer(_receiver, amounts_base[i]) + + # transfer withdrawn metapool tokens to caller + if _amounts[0] > 0: + coin: address = StableSwapMetaNG(_pool).coins(0) + ERC20(coin).transfer(_receiver, _amounts[0]) + + return burn_amount diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index be0c694c..8026a51e 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -139,7 +139,6 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: - with open(contract_file, "r") as f: source = f.read() @@ -159,7 +158,6 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): - deployed_contract = deployments[network][contract_designation] if not deployed_contract: @@ -180,7 +178,6 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo def deploy_infra(network, url, account, fork=False): - logger.log(f"Deploying on {network} ...") if fork: @@ -192,9 +189,7 @@ def deploy_infra(network, url, account, fork=False): boa.set_env(NetworkEnv(url)) boa.env.add_account(Account.from_key(os.environ[account])) for _network, data in deploy_utils.curve_dao_network_settings.items(): - if _network in network: - owner = data.dao_ownership_contract fee_receiver = data.fee_receiver_address @@ -252,13 +247,7 @@ def deploy_infra(network, url, account, fork=False): def main(): - - deploy_infra( - "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], - "FIDDYDEPLOYER", - fork=False, - ) + deploy_infra("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False) if __name__ == "__main__": diff --git a/scripts/deploy_pool.py b/scripts/deploy_pool.py index 885d6946..3965f70c 100644 --- a/scripts/deploy_pool.py +++ b/scripts/deploy_pool.py @@ -15,63 +15,27 @@ deployments = { # Ethereum - "ethereum:sepolia": { - "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", - }, - "ethereum:mainnet": { - "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - }, + "ethereum:sepolia": {"factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81"}, + "ethereum:mainnet": {"factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf"}, # Layer 2 - "arbitrum:mainnet": { - "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", - }, - "optimism:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "base:mainnet": { - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - }, - "linea:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "scroll:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "zksync:mainnet": { - "factory": "", - }, - "pzkevm:mainnet": { - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - }, + "arbitrum:mainnet": {"factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b"}, + "optimism:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "base:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, + "linea:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "scroll:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "zksync:mainnet": {"factory": ""}, + "pzkevm:mainnet": {"factory": "0xd2002373543Ce3527023C75e7518C274A51ce712"}, "mantle:mainnet": {"factory": ""}, # Layer 1 - "gnosis:mainnet": { - "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - }, - "polygon:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "avax:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "ftm:mainnet": { - "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", - }, - "bsc:mainnet": { - "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - }, - "celo:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "kava:mainnet": { - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - }, - "aurora:mainnet": { - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - }, - "tron:mainnet": { - "factory": "", - }, + "gnosis:mainnet": {"factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8"}, + "polygon:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "avax:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "ftm:mainnet": {"factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b"}, + "bsc:mainnet": {"factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B"}, + "celo:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "kava:mainnet": {"factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585"}, + "aurora:mainnet": {"factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E"}, + "tron:mainnet": {"factory": ""}, } @@ -130,7 +94,6 @@ class PoolSettings: def deploy_pool(network, url, account, pool_type, fork): - logger.log(f"Deploying pool on {network} ...") if fork: @@ -158,7 +121,6 @@ def deploy_pool(network, url, account, pool_type, fork): def deploy_gauge(network, url, account, pool_addr, fork): - logger.log(f"Deploying gauge for pool {pool_addr} on {network} ...") if fork: @@ -181,7 +143,6 @@ def deploy_gauge(network, url, account, pool_addr, fork): def deploy_pool_and_gauge(network, url, account, pool_type, fork): - logger.log(f"Deploying pool on {network} ...") if fork: @@ -211,7 +172,6 @@ def deploy_pool_and_gauge(network, url, account, pool_type, fork): def main(): - fork = False deploy_pool_and_gauge("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", "plain", fork) deploy_pool_and_gauge("ethereum:mainnet", "http://localhost:9090/", "FIDDYDEPLOYER", "meta", fork) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index ca607c85..fca78790 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -13,7 +13,6 @@ def deploy_proxy_admin(network, url, account, fork=False): - logger.log(f"Deploying ProxyAdmin for {network} ...") if fork: @@ -35,12 +34,7 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): - deploy_proxy_admin( - ":mainnet", - os.environ["RPC_"], - "", - fork=False, - ) + deploy_proxy_admin(":mainnet", os.environ["RPC_"], "", fork=False) if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index b7af3632..84ff7a58 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -58,8 +58,7 @@ class CurveNetworkSettings: fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "zksync:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1", + dao_ownership_contract="", fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1" ), "pzkevm:mainnet": CurveNetworkSettings( dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", @@ -98,10 +97,7 @@ class CurveNetworkSettings: dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), - "tron:mainnet": CurveNetworkSettings( - dao_ownership_contract="", - fee_receiver_address="", - ), + "tron:mainnet": CurveNetworkSettings(dao_ownership_contract="", fee_receiver_address=""), "mantle:mainnet": CurveNetworkSettings( dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", diff --git a/scripts/set_up_base_pools.py b/scripts/set_up_base_pools.py index 06d127ed..bcb57086 100644 --- a/scripts/set_up_base_pools.py +++ b/scripts/set_up_base_pools.py @@ -75,20 +75,14 @@ class BasePoolSettings: BasePoolSettings( # 2pool pool="0x7f90122BF0700F9E7e1F688fe926940E8839F353", lp_token="0x7f90122BF0700F9E7e1F688fe926940E8839F353", - coins=[ - "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", - "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", - ], + coins=["0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"], asset_types=[0, 0], n_coins=2, ), BasePoolSettings( # fraxbp pool="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", lp_token="0xC9B8a3FDECB9D5b218d02555a8Baf332E5B740d5", - coins=[ - "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", - "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", - ], + coins=["0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"], asset_types=[0, 0], n_coins=2, ), @@ -108,10 +102,7 @@ class BasePoolSettings: BasePoolSettings( # fraxbp pool="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", lp_token="0x29A3d66B30Bc4AD674A4FDAF27578B64f6afbFe7", - coins=[ - "0x2E3D870790dC77A83DD1d18184Acc7439A53f475", - "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", - ], + coins=["0x2E3D870790dC77A83DD1d18184Acc7439A53f475", "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"], asset_types=[0, 0], n_coins=2, ), @@ -127,13 +118,12 @@ class BasePoolSettings: ], asset_types=[0, 0, 0], n_coins=3, - ), + ) ], } def set_up_base_pools(network, url, account, fork: bool = False): - logger.log(f"Connecting to {network} ...") if fork: boa.env.fork(url) @@ -154,12 +144,7 @@ def set_up_base_pools(network, url, account, fork: bool = False): if base_pool_data: # check if network has base pools: for data in base_pool_data: if to_checksum_address(data.pool) not in onboarded_base_pools: - factory.add_base_pool( - data.pool, - data.lp_token, - data.asset_types, - data.n_coins, - ) + factory.add_base_pool(data.pool, data.lp_token, data.asset_types, data.n_coins) logger.log(f"Added {data.pool} to factory {factory_address} on {network}.") else: logger.log(f"{data.pool} is already configured as a base pool in factory {factory_address}.") @@ -170,33 +155,12 @@ def set_up_base_pools(network, url, account, fork: bool = False): def main(): - fork = False - set_up_base_pools( - "ethereum:mainnet", - os.environ["RPC_ETHEREUM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "arbitrum:mainnet", - os.environ["RPC_ARBITRUM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "optimism:mainnet", - os.environ["RPC_OPTIMISM"], - "FIDDYDEPLOYER", - fork=fork, - ) - set_up_base_pools( - "gnosis:mainnet", - os.environ["RPC_GNOSIS"], - "FIDDYDEPLOYER", - fork=fork, - ) + set_up_base_pools("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("arbitrum:mainnet", os.environ["RPC_ARBITRUM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("optimism:mainnet", os.environ["RPC_OPTIMISM"], "FIDDYDEPLOYER", fork=fork) + set_up_base_pools("gnosis:mainnet", os.environ["RPC_GNOSIS"], "FIDDYDEPLOYER", fork=fork) if __name__ == "__main__": diff --git a/scripts/vote_utils.py b/scripts/vote_utils.py index fd7defe7..b9043e9a 100644 --- a/scripts/vote_utils.py +++ b/scripts/vote_utils.py @@ -47,7 +47,6 @@ def prepare_evm_script(target: Dict, actions: List[Tuple]) -> str: evm_script = "0x00000001" for address, fn_name, *args in actions: - contract = ape.Contract(address) fn = getattr(contract, fn_name) calldata = fn.as_transaction(*args, sender=agent.address, gas_price=0).data diff --git a/tests/fixtures/contracts.py b/tests/fixtures/contracts.py index 666c35e8..50a003e0 100644 --- a/tests/fixtures/contracts.py +++ b/tests/fixtures/contracts.py @@ -42,6 +42,11 @@ def zap_deployer(): return boa.load_partial("contracts/mocks/Zap.vy") +@pytest.fixture(scope="session") +def meta_zap_ng_deployer(): + return boa.load_partial("contracts/main/MetaZapNG.vy") + + @pytest.fixture(scope="session") def gauge_deployer(): return boa.load_partial("contracts/main/LiquidityGauge.vy") diff --git a/tests/pools/meta/test_meta_zap_ng_base.py b/tests/pools/meta/test_meta_zap_ng_base.py new file mode 100644 index 00000000..1f70c53a --- /dev/null +++ b/tests/pools/meta/test_meta_zap_ng_base.py @@ -0,0 +1,205 @@ +import warnings + +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + +warnings.filterwarnings("ignore") + + +BASE_N_COINS = 5 + + +@pytest.fixture(scope="module") +def ng_base_pool_decimals(): + return [18] * BASE_N_COINS + + +@pytest.fixture() +def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer): + return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(BASE_N_COINS)] + + +@pytest.fixture() +def meta_token(erc20_deployer): + return erc20_deployer.deploy("OTA", "OTA", 18) + + +@pytest.fixture() +def tokens_all(meta_token, ng_base_pool_tokens): + return [meta_token] + ng_base_pool_tokens + + +@pytest.fixture() +def ng_base_pool(deployer, factory, ng_base_pool_tokens, zero_address, amm_deployer, set_pool_implementations, alice): + pool_size = len(ng_base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in ng_base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + [tkn.asset_type() for tkn in ng_base_pool_tokens], + method_ids, + oracles, + ) + + base_pool = amm_deployer.at(pool) + + amt_to_deposit = 10**18 + + for token in ng_base_pool_tokens: + mint_for_testing(alice, amt_to_deposit, token, False) + token.approve(base_pool.address, 2**256 - 1, sender=alice) + + out_amount = base_pool.add_liquidity([amt_to_deposit] * len(ng_base_pool_tokens), 0, sender=alice) + assert base_pool.totalSupply() == out_amount + return base_pool + + +@pytest.fixture() +def ng_metapool_tokens(meta_token, ng_base_pool): + return [meta_token, ng_base_pool] + + +@pytest.fixture() +def add_ng_base_pool(owner, factory, ng_base_pool, ng_base_pool_tokens): + with boa.env.prank(owner): + factory.add_base_pool( + ng_base_pool.address, ng_base_pool.address, [0] * len(ng_base_pool_tokens), len(ng_base_pool_tokens) + ) + + +@pytest.fixture() +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + ng_base_pool, + meta_deployer, + add_ng_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + asset_type = meta_token.asset_type() + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + ng_base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + asset_type, # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return meta_deployer.at(pool) + + +@pytest.fixture() +def zap(meta_zap_ng_deployer): + return meta_zap_ng_deployer.deploy() + + +@pytest.fixture() +def swap(zap, empty_swap, charlie, tokens_all): + to_deposit = 10**18 + + for token in tokens_all: + mint_for_testing(charlie, to_deposit, token, False) + token.approve(zap.address, 2**256 - 1, sender=charlie) + + out_amount = zap.add_liquidity(empty_swap.address, [to_deposit] * len(tokens_all), 0, sender=charlie) + assert out_amount > 0 + assert 0 not in empty_swap.get_balances() + assert empty_swap.totalSupply() > 0 + + return empty_swap + + +def test_calc_amts_add(zap, swap, charlie, tokens_all, ng_base_pool): + deposit_amount = 10**18 + for token in tokens_all: + mint_for_testing(charlie, deposit_amount, token, False) + token.approve(zap.address, 2**256 - 1, sender=charlie) + + deposit_amounts = [deposit_amount] * len(tokens_all) + + calc_amt_zap = zap.calc_token_amount(swap.address, deposit_amounts, True) + out_amount = zap.add_liquidity(swap.address, deposit_amounts, 0, sender=charlie) + + assert calc_amt_zap == out_amount + + +def test_calc_amts_remove_imbalance(zap, swap, meta_token, ng_base_pool_tokens, ng_base_pool, charlie, tokens_all): + initial_balance = swap.balanceOf(charlie) + amounts_to_remove = [initial_balance // len(tokens_all)] * len(tokens_all) + + swap.approve(zap, 2**256 - 1, sender=charlie) + max_burn = swap.balanceOf(charlie) + zap.remove_liquidity_imbalance(swap, amounts_to_remove, max_burn, sender=charlie) + + # check if charlie received what was wanted: + for i, token in enumerate(tokens_all): + assert token.balanceOf(charlie) == amounts_to_remove[i] + + # bob is the only LP, total supply is affected in the same way as his balance + assert swap.balanceOf(charlie) < initial_balance + assert swap.balanceOf(charlie) >= initial_balance - max_burn + + assert swap.balanceOf(zap) == 0 + assert swap.balanceOf(charlie) == swap.totalSupply() + + +def test_calc_amts_remove(zap, swap, charlie, tokens_all, meta_token, ng_base_pool, ng_base_pool_tokens): + charlie_bal_before = [] + for _t in tokens_all: + charlie_bal_before.append(_t.balanceOf(charlie)) + + charlie_lp_bal_before = swap.balanceOf(charlie) + + with boa.env.anchor(): + amts_received = swap.remove_liquidity(charlie_lp_bal_before, [0, 0], sender=charlie) + base_amts_received = ng_base_pool.remove_liquidity( + amts_received[1], [0] * ng_base_pool.N_COINS(), sender=charlie + ) + total_expected_received = [amts_received[0]] + base_amts_received + + swap.approve(zap, 2**256 - 1, sender=charlie) + total_received_amount = zap.remove_liquidity( + swap.address, charlie_lp_bal_before, [0] * len(tokens_all), sender=charlie + ) + + # tokens owned by zap: + zap_balances = [] + for token in tokens_all: + zap_balances.append(token.balanceOf(zap)) + + charlie_bal_after = [] + for _t in tokens_all: + charlie_bal_after.append(_t.balanceOf(charlie)) + + for i in range(len(tokens_all)): + assert total_received_amount[i] == total_expected_received[i] From f70a77a2a56238d1a4fa1e86ca986429222713e0 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:39:08 +0100 Subject: [PATCH 310/337] add tests for remove one coin --- tests/pools/meta/test_meta_zap_ng_base.py | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/pools/meta/test_meta_zap_ng_base.py b/tests/pools/meta/test_meta_zap_ng_base.py index 1f70c53a..0fe120c7 100644 --- a/tests/pools/meta/test_meta_zap_ng_base.py +++ b/tests/pools/meta/test_meta_zap_ng_base.py @@ -203,3 +203,39 @@ def test_calc_amts_remove(zap, swap, charlie, tokens_all, meta_token, ng_base_po for i in range(len(tokens_all)): assert total_received_amount[i] == total_expected_received[i] + + +def test_calc_amts_remove_one_meta_coin(zap, swap, charlie, tokens_all, meta_token, ng_base_pool, ng_base_pool_tokens): + charlie_bal_before = [] + for _t in tokens_all: + charlie_bal_before.append(_t.balanceOf(charlie)) + + lp_to_remove = swap.balanceOf(charlie) // 10 + calc_amt_removed = zap.calc_withdraw_one_coin(swap.address, lp_to_remove, 0, sender=charlie) + swap.approve(zap, 2**256 - 1, sender=charlie) + + with boa.env.anchor(): + amts_received_swap = swap.remove_liquidity_one_coin(lp_to_remove, 0, 0, sender=charlie) + assert calc_amt_removed == amts_received_swap + + amts_received_zap = zap.remove_liquidity_one_coin(swap.address, lp_to_remove, 0, 0, sender=charlie) + + assert amts_received_zap == amts_received_swap + + +def test_calc_amts_remove_one_base_coin(zap, swap, charlie, tokens_all, meta_token, ng_base_pool, ng_base_pool_tokens): + charlie_bal_before = [] + for _t in tokens_all: + charlie_bal_before.append(_t.balanceOf(charlie)) + + lp_to_remove = swap.balanceOf(charlie) // 10 + calc_amt_removed = zap.calc_withdraw_one_coin(swap.address, lp_to_remove, 1, sender=charlie) + swap.approve(zap, 2**256 - 1, sender=charlie) + + with boa.env.anchor(): + amts_received_swap = swap.remove_liquidity_one_coin(lp_to_remove, 1, 0, sender=charlie) + amts_received_base = ng_base_pool.remove_liquidity_one_coin(amts_received_swap, 0, 0, sender=charlie) + assert calc_amt_removed == amts_received_base + + amts_received_zap = zap.remove_liquidity_one_coin(swap.address, lp_to_remove, 1, 0, sender=charlie) + assert amts_received_zap == amts_received_base From 03d6b77dca89ba76b8b19e6a7b65e4ed24cf4751 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:34:57 +0100 Subject: [PATCH 311/337] fraxtal deployment --- scripts/deploy_infra.py | 31 ++++++++++++++++++++++++++++++- scripts/deployment_utils.py | 4 ++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 8026a51e..419ec5cf 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -19,6 +19,7 @@ "meta_amm": "0xede71F77d7c900dCA5892720E76316C6E575F0F7", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "gauge": "0x38D9BdA812da2C68dFC6aDE85A7F7a54E77F8325", + "zap": "", }, "ethereum:sepolia": { "math": "0x2cad7b3e78e10bcbf2cc443ddd69ca8bcc09a758", @@ -26,6 +27,7 @@ "plain_amm": "0xE12374F193f91f71CE40D53E0db102eBaA9098D5", "meta_amm": "0xB00E89EaBD59cD3254c88E390103Cf17E914f678", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", + "zap": "", }, # Layer 2 "arbitrum:mainnet": { @@ -34,6 +36,7 @@ "plain_amm": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", "meta_amm": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", + "zap": "", }, "optimism:mainnet": { "math": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", @@ -41,6 +44,7 @@ "plain_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "meta_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "", }, "base:mainnet": { "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", @@ -48,6 +52,7 @@ "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "", }, "linea:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", @@ -55,6 +60,7 @@ "plain_amm": "0xa7b9d886a9a374a1c86dc52d2ba585c5cdfdac26", "meta_amm": "0xf3a6aa40cf048a3960e9664847e9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "", }, "scroll:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", @@ -62,6 +68,7 @@ "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "", }, "pzkevm:mainnet": { "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", @@ -69,6 +76,7 @@ "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "", }, # Layer 1 "gnosis:mainnet": { @@ -77,6 +85,7 @@ "plain_amm": "0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a", "meta_amm": "0xC1b393EfEF38140662b91441C6710Aa704973228", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "zap": "", }, "polygon:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", @@ -84,6 +93,7 @@ "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "", }, "avax:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", @@ -91,6 +101,7 @@ "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "", }, "ftm:mainnet": { "math": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", @@ -98,6 +109,7 @@ "plain_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", "meta_amm": "0x046207cB759F527b6c10C2D61DBaca45513685CC", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + "zap": "", }, "bsc:mainnet": { "math": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", @@ -105,6 +117,7 @@ "plain_amm": "0x505d666E4DD174DcDD7FA090ed95554486d2Be44", "meta_amm": "0x5a8C93EE12a8Df4455BA111647AdA41f29D5CfcC", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "zap": "", }, "celo:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", @@ -112,6 +125,7 @@ "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "", }, "kava:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", @@ -119,6 +133,7 @@ "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "", }, "aurora:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", @@ -126,6 +141,15 @@ "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "", + }, + "fraxtal:mainnet": { + "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "meta_amm": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", }, "mantle:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", @@ -133,6 +157,7 @@ "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "", "factory_ctor": "000000000000000000000000f3a431008396df8a8b2df492c913706bdb0874ef0000000000000000000000002d12d0907a388811e3aa855a550f959501d303ee", # noqa:E501 }, } @@ -203,6 +228,7 @@ def deploy_infra(network, url, account, fork=False): views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) plain_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNG.vy", network) meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) + zap_contract_obj = set_evm_version("./contracts/main/MetaZapNG.vy", network) # deploy non-blueprint contracts: math_contract = check_and_deploy(math_contract_obj, "math", network) @@ -217,6 +243,9 @@ def deploy_infra(network, url, account, fork=False): args = [fee_receiver, deploy_utils.FIDDYDEPLOYER] factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) + # zap: + check_and_deploy(zap_contract_obj, "zap", network) + # Set up AMM implementations: if not factory.views_implementation() == views_contract.address: factory.set_views_implementation(views_contract.address) @@ -247,7 +276,7 @@ def deploy_infra(network, url, account, fork=False): def main(): - deploy_infra("ethereum:mainnet", os.environ["RPC_ETHEREUM"], "FIDDYDEPLOYER", fork=False) + deploy_infra(":mainnet", os.environ["RPC_"], "", fork=False) if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 84ff7a58..188326af 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -97,6 +97,10 @@ class CurveNetworkSettings: dao_ownership_contract="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", fee_receiver_address="0x98B4029CaBEf7Fd525A36B0BF8555EC1d42ec0B6", ), + "fraxtal:mainnet": CurveNetworkSettings( + dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", # proxy + fee_receiver_address="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + ), "tron:mainnet": CurveNetworkSettings(dao_ownership_contract="", fee_receiver_address=""), "mantle:mainnet": CurveNetworkSettings( dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", From 4512468b4c374b5f068717dd6673ed06df770ece Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:46:22 +0100 Subject: [PATCH 312/337] fix: push_pr ci --- .github/workflows/push_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push_pr.yml b/.github/workflows/push_pr.yml index afa22ae8..2c4c65ca 100644 --- a/.github/workflows/push_pr.yml +++ b/.github/workflows/push_pr.yml @@ -1,6 +1,6 @@ on: release: - types: [ created ] + types: [created] name: on-creating-new-tag @@ -30,7 +30,7 @@ jobs: git config user.name "curvefi" git remote update git fetch - git checkout master + git checkout main git add deployments git commit -m "chore: add release file - $TITLE" git push From ec972b331da21d919f78943e00bf9398970eca54 Mon Sep 17 00:00:00 2001 From: curvefi Date: Tue, 13 Feb 2024 13:52:14 +0000 Subject: [PATCH 313/337] chore: add release file - Fraxtal --- deployments/release-Fraxtal-13-02-2024.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 deployments/release-Fraxtal-13-02-2024.txt diff --git a/deployments/release-Fraxtal-13-02-2024.txt b/deployments/release-Fraxtal-13-02-2024.txt new file mode 100644 index 00000000..a53b270b --- /dev/null +++ b/deployments/release-Fraxtal-13-02-2024.txt @@ -0,0 +1 @@ +Fraxtal deployment: math: [0x506F594ceb4E33F5161139bAe3Ee911014df9f7f](www.fraxscan.com/address/0x506F594ceb4E33F5161139bAe3Ee911014df9f7f) views: [0x87FE17697D0f14A222e8bEf386a0860eCffDD617](www.fraxscan.com/address/0x87FE17697D0f14A222e8bEf386a0860eCffDD617) plain_amm: [0x1764ee18e8B3ccA4787249Ceb249356192594585](www.fraxscan.com/address/0x1764ee18e8B3ccA4787249Ceb249356192594585) meta_amm: [0x5eeE3091f747E60a045a2E715a4c71e600e31F6E](www.fraxscan.com/address/0x5eeE3091f747E60a045a2E715a4c71e600e31F6E) factory: [0xd2002373543Ce3527023C75e7518C274A51ce712](www.fraxscan.com/address/0xd2002373543Ce3527023C75e7518C274A51ce712) zap: [0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b](www.fraxscan.com/address/0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b) Ethereum meta-ng zap: [0xe07a16358aa878cbda2d49a88e5106871e0db307](https://etherscan.io/address/0xe07a16358aa878cbda2d49a88e5106871e0db307#code) From 0b351ad0a7a8f849db224a68f802c92dbf55da17 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 13 Mar 2024 16:04:26 +0100 Subject: [PATCH 314/337] docs: fixed typo --- contracts/main/CurveStableSwapNGViews.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 6ff702dd..ac58adcc 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -100,7 +100,7 @@ def get_dx_underlying( N_COINS: uint256 = StableSwapNG(pool).N_COINS() base_pool_has_static_fee: bool = self._has_static_fee(BASE_POOL) - # CASE 1: Swap does not involve Metapool at all. In this case, we kindly as the user + # CASE 1: Swap does not involve Metapool at all. In this case, we kindly ask the user # to use the right pool for their swaps. if min(i, j) > 0: raise "Not a Metapool Swap. Use Base pool." From bb86da1b9b49632a88f6ca0c55028c10d6a0e6cd Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 13 Mar 2024 16:05:06 +0100 Subject: [PATCH 315/337] fix: using suggested correction Co-Authored-By: fiddyresearch <11488427+bout3fiddy@users.noreply.github.com> --- contracts/main/CurveStableSwapNGViews.vy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index ac58adcc..c891703d 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -456,14 +456,15 @@ def _base_calc_token_amount( base_pool: address, is_deposit: bool ) -> uint256: + base_pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) - if base_n_coins == 2: + if base_n_coins == 2 and not base_pool_is_ng: base_inputs: uint256[2] = empty(uint256[2]) base_inputs[base_i] = dx return StableSwap2(base_pool).calc_token_amount(base_inputs, is_deposit) - elif base_n_coins == 3: + elif base_n_coins == 3 and not base_pool_is_ng: base_inputs: uint256[3] = empty(uint256[3]) base_inputs[base_i] = dx From 5e7fdc6b29adddad9d8383f0a934d5d8514fa3fd Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 16 Mar 2024 23:26:30 +0100 Subject: [PATCH 316/337] test: ensuring calls do not revert anymore --- tests/pools/meta/test_meta_new_ng_base.py | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index cfabc129..28f7f662 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -5,17 +5,19 @@ from tests.utils.tokens import mint_for_testing -BASE_N_COINS = 5 +@pytest.fixture(params=[2, 3, 4, 5], scope="module") +def base_n_coins(request): + return request.param @pytest.fixture(scope="module") -def ng_base_pool_decimals(): - return [18] * BASE_N_COINS +def ng_base_pool_decimals(base_n_coins): + return [18] * base_n_coins @pytest.fixture() -def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer): - return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(BASE_N_COINS)] +def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer, base_n_coins): + return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(base_n_coins)] @pytest.fixture() @@ -109,20 +111,20 @@ def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, n @pytest.fixture() def deposit_amounts( - meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob + meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob, base_n_coins ): _deposit_amounts = [] - INITIAL_AMOUNT = 1_000_000 * BASE_N_COINS - _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** meta_token.decimals()) + INITIAL_AMOUNT = 1_000_000 * base_n_coins + _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** meta_token.decimals()) def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = INITIAL_AMOUNT // BASE_N_COINS + amount = INITIAL_AMOUNT // base_n_coins with boa.env.prank(user): amounts = [amount * 10**d for d in base_pool_decimals] base_pool.add_liquidity(amounts, 0) add_base_pool_liquidity(bob, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals) - _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** ng_base_pool.decimals()) + _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** ng_base_pool.decimals()) ng_base_pool.approve(empty_swap, 2**256 - 1, sender=bob) return _deposit_amounts @@ -140,3 +142,13 @@ def test_exchange_underlying_ng_base(swap, bob, sending, receiving): actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert expected_out == actual_out + +@pytest.fixture +def coins_range(base_n_coins): + return range(1, base_n_coins) + +def test_exchange_underlying_preview(swap, bob, coins_range, base_n_coins): + receiving = 0 + for sending in coins_range: + # these calls used to revert before the fix + swap.get_dy_underlying(sending, receiving, 10**19) \ No newline at end of file From 7eaaa71617b63201992b8fe566d9ae4acdf9197b Mon Sep 17 00:00:00 2001 From: Alberto Date: Sun, 17 Mar 2024 18:47:02 +0100 Subject: [PATCH 317/337] refactor: moved fix #43 test to separate file fixtures changes were messing with the other tests --- .../pools/meta/test_get_dy_underlying_fix.py | 147 ++++++++++++++++++ tests/pools/meta/test_meta_new_ng_base.py | 34 ++-- 2 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 tests/pools/meta/test_get_dy_underlying_fix.py diff --git a/tests/pools/meta/test_get_dy_underlying_fix.py b/tests/pools/meta/test_get_dy_underlying_fix.py new file mode 100644 index 00000000..b354db0c --- /dev/null +++ b/tests/pools/meta/test_get_dy_underlying_fix.py @@ -0,0 +1,147 @@ +# =================== Test for issue #43 fix =================== +import pytest + +import boa +import pytest + +from tests.utils.tokens import mint_for_testing + +@pytest.fixture(params=[2, 3, 4, 5], scope="module") +def base_n_coins(request): + return request.param + + +@pytest.fixture(scope="module") +def ng_base_pool_decimals(base_n_coins): + return [18] * base_n_coins + + +@pytest.fixture() +def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer, base_n_coins): + return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(base_n_coins)] + + +@pytest.fixture() +def meta_token(erc20_deployer): + return erc20_deployer.deploy("OTA", "OTA", 18) + + +@pytest.fixture() +def ng_base_pool(deployer, factory, ng_base_pool_tokens, zero_address, amm_deployer, set_pool_implementations): + pool_size = len(ng_base_pool_tokens) + offpeg_fee_multiplier = 20000000000 + method_ids = [bytes(b"")] * pool_size + oracles = [zero_address] * pool_size + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_plain_pool( + "test", + "test", + [t.address for t in ng_base_pool_tokens], + A, + fee, + offpeg_fee_multiplier, + 866, + 0, + [tkn.asset_type() for tkn in ng_base_pool_tokens], + method_ids, + oracles, + ) + + return amm_deployer.at(pool) + + +@pytest.fixture() +def ng_metapool_tokens(meta_token, ng_base_pool): + return [meta_token, ng_base_pool] + + +@pytest.fixture() +def add_ng_base_pool(owner, factory, ng_base_pool, ng_base_pool_tokens): + with boa.env.prank(owner): + factory.add_base_pool( + ng_base_pool.address, ng_base_pool.address, [0] * len(ng_base_pool_tokens), len(ng_base_pool_tokens) + ) + + +@pytest.fixture() +def empty_swap( + deployer, + factory, + zero_address, + meta_token, + ng_base_pool, + meta_deployer, + add_ng_base_pool, + set_metapool_implementations, +): + method_id = bytes(b"") + oracle = zero_address + offpeg_fee_multiplier = 20000000000 + A = 1000 + fee = 3000000 + + with boa.env.prank(deployer): + pool = factory.deploy_metapool( + ng_base_pool.address, # _base_pool: address + "test", # _name: String[32], + "test", # _symbol: String[10], + meta_token.address, # _coin: address, + A, # _A: uint256, + fee, # _fee: uint256, + offpeg_fee_multiplier, + 866, # _ma_exp_time: uint256, + 0, # _implementation_idx: uint256 + meta_token.asset_type(), # _asset_type: uint8 + method_id, # _method_id: bytes4 + oracle, # _oracle: address + ) + + return meta_deployer.at(pool) + + +@pytest.fixture() +def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, ng_base_pool): + for token in [meta_token] + ng_base_pool_tokens: + mint_for_testing(bob, 10**25, token) + token.approve(empty_swap, 2**256 - 1, sender=bob) + token.approve(ng_base_pool, 2**256 - 1, sender=bob) + + +@pytest.fixture() +def deposit_amounts( + meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob, base_n_coins +): + _deposit_amounts = [] + INITIAL_AMOUNT = 1_000_000 * base_n_coins + _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** meta_token.decimals()) + + def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): + amount = INITIAL_AMOUNT // base_n_coins + with boa.env.prank(user): + amounts = [amount * 10**d for d in base_pool_decimals] + base_pool.add_liquidity(amounts, 0) + + add_base_pool_liquidity(bob, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals) + _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** ng_base_pool.decimals()) + ng_base_pool.approve(empty_swap, 2**256 - 1, sender=bob) + return _deposit_amounts + + +@pytest.fixture() +def swap(empty_swap, bob, deposit_amounts): + empty_swap.add_liquidity(deposit_amounts, 0, bob, sender=bob) + return empty_swap + + +@pytest.fixture +def coins_range(base_n_coins): + return range(1, base_n_coins) + +def test_exchange_underlying_preview(swap, coins_range): + receiving = 0 + for sending in coins_range: + # these calls used to revert before the fix + swap.get_dy_underlying(sending, receiving, 10**19) diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index 28f7f662..01b473f7 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -5,19 +5,16 @@ from tests.utils.tokens import mint_for_testing -@pytest.fixture(params=[2, 3, 4, 5], scope="module") -def base_n_coins(request): - return request.param - +BASE_N_COINS = 5 @pytest.fixture(scope="module") -def ng_base_pool_decimals(base_n_coins): - return [18] * base_n_coins +def ng_base_pool_decimals(): + return [18] * BASE_N_COINS @pytest.fixture() -def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer, base_n_coins): - return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(base_n_coins)] +def ng_base_pool_tokens(ng_base_pool_decimals, erc20_deployer): + return [erc20_deployer.deploy(f"tkn{i}", f"tkn{i}", ng_base_pool_decimals[i]) for i in range(BASE_N_COINS)] @pytest.fixture() @@ -111,20 +108,20 @@ def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, n @pytest.fixture() def deposit_amounts( - meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob, base_n_coins + meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob ): _deposit_amounts = [] - INITIAL_AMOUNT = 1_000_000 * base_n_coins - _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** meta_token.decimals()) + INITIAL_AMOUNT = 1_000_000 * BASE_N_COINS + _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** meta_token.decimals()) def add_base_pool_liquidity(user, base_pool, base_pool_tokens, base_pool_decimals): - amount = INITIAL_AMOUNT // base_n_coins + amount = INITIAL_AMOUNT // BASE_N_COINS with boa.env.prank(user): amounts = [amount * 10**d for d in base_pool_decimals] base_pool.add_liquidity(amounts, 0) add_base_pool_liquidity(bob, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals) - _deposit_amounts.append(INITIAL_AMOUNT // base_n_coins * 10 ** ng_base_pool.decimals()) + _deposit_amounts.append(INITIAL_AMOUNT // BASE_N_COINS * 10 ** ng_base_pool.decimals()) ng_base_pool.approve(empty_swap, 2**256 - 1, sender=bob) return _deposit_amounts @@ -142,13 +139,4 @@ def test_exchange_underlying_ng_base(swap, bob, sending, receiving): actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert expected_out == actual_out - -@pytest.fixture -def coins_range(base_n_coins): - return range(1, base_n_coins) - -def test_exchange_underlying_preview(swap, bob, coins_range, base_n_coins): - receiving = 0 - for sending in coins_range: - # these calls used to revert before the fix - swap.get_dy_underlying(sending, receiving, 10**19) \ No newline at end of file + assert expected_out == actual_out \ No newline at end of file From b43b32762bfcc270d54a7dbf579708c5c1c39a47 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sun, 17 Mar 2024 18:48:04 +0100 Subject: [PATCH 318/337] test: verifying original error is fixed --- tests/pools/meta/test_get_dy_underlying_fix.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/pools/meta/test_get_dy_underlying_fix.py b/tests/pools/meta/test_get_dy_underlying_fix.py index b354db0c..3c53e42c 100644 --- a/tests/pools/meta/test_get_dy_underlying_fix.py +++ b/tests/pools/meta/test_get_dy_underlying_fix.py @@ -145,3 +145,12 @@ def test_exchange_underlying_preview(swap, coins_range): for sending in coins_range: # these calls used to revert before the fix swap.get_dy_underlying(sending, receiving, 10**19) + +def test_broken_pool_is_fixed(forked_chain, meta_deployer, views_deployer): + BROKEN_SWAP = '0x9e10f9Fb6F0D32B350CEe2618662243d4f24C64a' + BROKEN_VIEW ='0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA' + + # testing fix for the first instance of the error reported + metapool = meta_deployer.at(BROKEN_SWAP) + views_deployer.at(BROKEN_VIEW) + metapool.get_dy_underlying(1, 0, 50000000) \ No newline at end of file From 232ba081be2bf6cba5e4068225cd80fc457b58d9 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sun, 17 Mar 2024 19:34:14 +0100 Subject: [PATCH 319/337] fix: `calc_token_amount` correction --- contracts/main/CurveStableSwapNGViews.vy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index c891703d..c49e7270 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -283,7 +283,7 @@ def calc_token_amount( else: difference = new_balance - ideal_balance - xs = old_balances[i] + new_balance + xs = rates[i] * (old_balances[i] + new_balance) / PRECISION _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR From 9eee97598b20d5ce5baa193df68db4a783f8bd71 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 20 Mar 2024 11:53:26 +0100 Subject: [PATCH 320/337] test: added assertions for fix --- tests/pools/liquidity/test_add_liquidity.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/pools/liquidity/test_add_liquidity.py b/tests/pools/liquidity/test_add_liquidity.py index 72a3ac28..b411a4c5 100644 --- a/tests/pools/liquidity/test_add_liquidity.py +++ b/tests/pools/liquidity/test_add_liquidity.py @@ -18,7 +18,20 @@ def test_add_liquidity( pool_token_types, metapool_token_type, ): - swap.add_liquidity(deposit_amounts, 0, sender=bob) + if pool_type == 1: + pytest.xfail("pool_type = meta - should be fixed") + + if pool_token_types == (2, 2) and pool_type == 0: + pytest.xfail("pool_token_types = rebase-rebase - should be fixed") + # similar to issue #44 there is probably some miscalculation in the view contract + + calculated_output = swap.calc_token_amount(deposit_amounts, True) + + returned_output = swap.add_liquidity(deposit_amounts, 0, sender=bob) + + # estimation should corresond to the reported amount + assert calculated_output == returned_output + is_ideal = True if pool_type == 0: @@ -68,6 +81,9 @@ def test_add_one_coin( metapool_token_type, idx, ): + if pool_type == 1: + pytest.xfail("pool_type = meta - should be fixed") + amounts = [0] * len(pool_tokens) amounts[idx] = deposit_amounts[idx] @@ -122,6 +138,8 @@ def test_min_amount_too_high(bob, swap, pool_type, deposit_amounts, pool_tokens) def test_event(bob, swap, pool_type, deposit_amounts, pool_tokens, pool_token_types, metapool_token_type): + if pool_type == 1 and metapool_token_type == 0: + pytest.xfail("pool_type = meta, meta token type = plain - should be fixed") size = 2 check_invariant = True if pool_type == 0: From 24fd6f3dd56af0fe6513cd11c8b25bffdd5d121a Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 20 Mar 2024 14:30:14 +0100 Subject: [PATCH 321/337] chore: linting to fix ci --- .../pools/meta/test_get_dy_underlying_fix.py | 20 +++++++++++++------ tests/pools/meta/test_meta_new_ng_base.py | 3 ++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/pools/meta/test_get_dy_underlying_fix.py b/tests/pools/meta/test_get_dy_underlying_fix.py index 3c53e42c..46245135 100644 --- a/tests/pools/meta/test_get_dy_underlying_fix.py +++ b/tests/pools/meta/test_get_dy_underlying_fix.py @@ -1,11 +1,10 @@ # =================== Test for issue #43 fix =================== -import pytest - import boa import pytest from tests.utils.tokens import mint_for_testing + @pytest.fixture(params=[2, 3, 4, 5], scope="module") def base_n_coins(request): return request.param @@ -112,7 +111,14 @@ def mint_and_approve_for_bob(meta_token, ng_base_pool_tokens, bob, empty_swap, n @pytest.fixture() def deposit_amounts( - meta_token, ng_base_pool, ng_base_pool_tokens, ng_base_pool_decimals, empty_swap, bob, mint_and_approve_for_bob, base_n_coins + meta_token, + ng_base_pool, + ng_base_pool_tokens, + ng_base_pool_decimals, + empty_swap, + bob, + mint_and_approve_for_bob, + base_n_coins, ): _deposit_amounts = [] INITIAL_AMOUNT = 1_000_000 * base_n_coins @@ -140,17 +146,19 @@ def swap(empty_swap, bob, deposit_amounts): def coins_range(base_n_coins): return range(1, base_n_coins) + def test_exchange_underlying_preview(swap, coins_range): receiving = 0 for sending in coins_range: # these calls used to revert before the fix swap.get_dy_underlying(sending, receiving, 10**19) + def test_broken_pool_is_fixed(forked_chain, meta_deployer, views_deployer): - BROKEN_SWAP = '0x9e10f9Fb6F0D32B350CEe2618662243d4f24C64a' - BROKEN_VIEW ='0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA' + BROKEN_SWAP = "0x9e10f9Fb6F0D32B350CEe2618662243d4f24C64a" + BROKEN_VIEW = "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA" # testing fix for the first instance of the error reported metapool = meta_deployer.at(BROKEN_SWAP) views_deployer.at(BROKEN_VIEW) - metapool.get_dy_underlying(1, 0, 50000000) \ No newline at end of file + metapool.get_dy_underlying(1, 0, 50000000) diff --git a/tests/pools/meta/test_meta_new_ng_base.py b/tests/pools/meta/test_meta_new_ng_base.py index 01b473f7..f1b24980 100644 --- a/tests/pools/meta/test_meta_new_ng_base.py +++ b/tests/pools/meta/test_meta_new_ng_base.py @@ -7,6 +7,7 @@ BASE_N_COINS = 5 + @pytest.fixture(scope="module") def ng_base_pool_decimals(): return [18] * BASE_N_COINS @@ -139,4 +140,4 @@ def test_exchange_underlying_ng_base(swap, bob, sending, receiving): actual_out = swap.exchange_underlying(sending, receiving, amount, 0, sender=bob) assert expected_out == actual_out - assert expected_out == actual_out \ No newline at end of file + assert expected_out == actual_out From 2159e095deecaf5fafc2f5dbc335c8abf60097e9 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:34:07 +0100 Subject: [PATCH 322/337] deployment in progress ... --- contracts/main/CurveStableSwapNGViews.vy | 2 +- contracts/main/MetaZapNG.vy | 1 + scripts/deploy_infra.py | 84 ++++++++++++++++-------- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index c49e7270..6bab1b8a 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -1,5 +1,5 @@ # pragma version 0.3.10 -# pragma evm-version shanghai +# pragma evm-version paris """ @title CurveStableSwapNGViews @author Curve.Fi diff --git a/contracts/main/MetaZapNG.vy b/contracts/main/MetaZapNG.vy index f3202b78..6bf60d4c 100644 --- a/contracts/main/MetaZapNG.vy +++ b/contracts/main/MetaZapNG.vy @@ -1,4 +1,5 @@ # pragma version 0.3.10 +# pragma evm-version paris """ @title MetaZapNG @author Curve.Fi diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 419ec5cf..613dcd05 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -14,7 +14,8 @@ # Ethereum "ethereum:mainnet": { "math": "0xc9CBC565A9F4120a2740ec6f64CC24AeB2bB3E5E", - "views": "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA", + # "views_old": "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA", + "views": "0x13526206545e2DC7CcfBaF28dC88F440ce7AD3e0", "plain_amm": "0xDCc91f930b42619377C200BA05b7513f2958b202", "meta_amm": "0xede71F77d7c900dCA5892720E76316C6E575F0F7", "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", @@ -23,7 +24,8 @@ }, "ethereum:sepolia": { "math": "0x2cad7b3e78e10bcbf2cc443ddd69ca8bcc09a758", - "views": "0x9d3975070768580f755D405527862ee126d0eA08", + # "views": "0x9d3975070768580f755D405527862ee126d0eA08", + "views": "", "plain_amm": "0xE12374F193f91f71CE40D53E0db102eBaA9098D5", "meta_amm": "0xB00E89EaBD59cD3254c88E390103Cf17E914f678", "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", @@ -32,7 +34,8 @@ # Layer 2 "arbitrum:mainnet": { "math": "0xD4a8bd4d59d65869E99f20b642023a5015619B34", - "views": "0x9293f068912bae932843a1bA01806c54f416019D", + # "views_old": "0x9293f068912bae932843a1bA01806c54f416019D", + "views": "0xDD7EBB1C49780519dD9755B8B1A23a6f42CE099E", "plain_amm": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", "meta_amm": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", @@ -40,7 +43,8 @@ }, "optimism:mainnet": { "math": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "views": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + # "views_old": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "views": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", "plain_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "meta_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -48,7 +52,8 @@ }, "base:mainnet": { "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + # "views_old": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", @@ -56,7 +61,8 @@ }, "linea:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "views": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", "plain_amm": "0xa7b9d886a9a374a1c86dc52d2ba585c5cdfdac26", "meta_amm": "0xf3a6aa40cf048a3960e9664847e9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -64,7 +70,8 @@ }, "scroll:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -72,7 +79,8 @@ }, "pzkevm:mainnet": { "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + # "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "views": "", "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", @@ -81,7 +89,8 @@ # Layer 1 "gnosis:mainnet": { "math": "0xFAbC421e3368D158d802684A217a83c083c94CeB", - "views": "0x0c59d36b23f809f8b6C7cb4c8C590a0AC103baEf", + # "views_old": "0x0c59d36b23f809f8b6C7cb4c8C590a0AC103baEf", + "views": "0x33e72383472f77B0C6d8F791D1613C75aE2C5915", "plain_amm": "0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a", "meta_amm": "0xC1b393EfEF38140662b91441C6710Aa704973228", "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", @@ -89,7 +98,8 @@ }, "polygon:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", @@ -97,7 +107,8 @@ }, "avax:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", @@ -105,7 +116,8 @@ }, "ftm:mainnet": { "math": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "views": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + # "views_old": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "views": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", "plain_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", "meta_amm": "0x046207cB759F527b6c10C2D61DBaca45513685CC", "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", @@ -113,7 +125,8 @@ }, "bsc:mainnet": { "math": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", - "views": "0x5Ea9DD3b6f042A34Df818C6c1324BC5A7c61427a", + # "views_old": "0x5Ea9DD3b6f042A34Df818C6c1324BC5A7c61427a", + "views": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", "plain_amm": "0x505d666E4DD174DcDD7FA090ed95554486d2Be44", "meta_amm": "0x5a8C93EE12a8Df4455BA111647AdA41f29D5CfcC", "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", @@ -121,7 +134,8 @@ }, "celo:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", @@ -129,7 +143,8 @@ }, "kava:mainnet": { "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "views": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", @@ -137,7 +152,8 @@ }, "aurora:mainnet": { "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -145,7 +161,8 @@ }, "fraxtal:mainnet": { "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "views": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + # "views_old": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "views": "0xFAbC421e3368D158d802684A217a83c083c94CeB", "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", "meta_amm": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", @@ -153,7 +170,8 @@ }, "mantle:mainnet": { "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + # "views_old": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + "views": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", @@ -228,7 +246,6 @@ def deploy_infra(network, url, account, fork=False): views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) plain_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNG.vy", network) meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) - zap_contract_obj = set_evm_version("./contracts/main/MetaZapNG.vy", network) # deploy non-blueprint contracts: math_contract = check_and_deploy(math_contract_obj, "math", network) @@ -244,22 +261,31 @@ def deploy_infra(network, url, account, fork=False): factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) # zap: - check_and_deploy(zap_contract_obj, "zap", network) + # zap_contract_obj = set_evm_version("./contracts/main/MetaZapNG.vy", network) + # check_and_deploy(zap_contract_obj, "zap", network) - # Set up AMM implementations: - if not factory.views_implementation() == views_contract.address: + # Set up AMM implementations:÷ + current_views_impl = factory.views_implementation() + if not current_views_impl == views_contract.address: + logger.log(f"Current views implementation: {current_views_impl}") factory.set_views_implementation(views_contract.address) logger.log(f"Set views implementation to: {views_contract.address}") - if not factory.math_implementation() == math_contract.address: + current_math_impl = factory.math_implementation() + if not current_math_impl == math_contract.address: + logger.log(f"Current math implementation: {current_math_impl}") factory.set_math_implementation(math_contract.address) logger.log(f"Set math implementation to: {math_contract.address}") - if not factory.pool_implementations(0) == plain_blueprint.address: + current_pool_impl = factory.pool_implementations(0) + if not current_pool_impl == plain_blueprint.address: + logger.log(f"Curent 'plain' pool impl at index 0: {current_pool_impl}") factory.set_pool_implementations(0, plain_blueprint.address) - logger.log(f"Set plain amm implementation to: {plain_blueprint.address}") + logger.log(f"Set plain amm implementation at index 0 to: {plain_blueprint.address}") - if not factory.metapool_implementations(0) == meta_blueprint.address: + current_metapool_impl = factory.metapool_implementations(0) + if not current_metapool_impl == meta_blueprint.address: + logger.log(f"Current metapool impl at index 0: {current_metapool_impl}") factory.set_metapool_implementations(0, meta_blueprint.address) logger.log(f"Set meta amm implementation to: {meta_blueprint.address}") @@ -276,7 +302,11 @@ def deploy_infra(network, url, account, fork=False): def main(): - deploy_infra(":mainnet", os.environ["RPC_"], "", fork=False) + deployer = "FIDDYDEPLOYER" + fork = False + network = "pzkevm" + rpc = f"https://lb.drpc.org/ogrpc?network=polygon-zkevm&dkey={os.environ['drpc_key']}" + deploy_infra(f"{network}:mainnet", rpc, deployer, fork=fork) if __name__ == "__main__": From d6192bff0485bd702a9c545207ff80a7d93da25f Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:23:03 +0200 Subject: [PATCH 323/337] update workflow --- .github/workflows/push_pr.yml | 52 ++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/push_pr.yml b/.github/workflows/push_pr.yml index 2c4c65ca..a3656705 100644 --- a/.github/workflows/push_pr.yml +++ b/.github/workflows/push_pr.yml @@ -1,8 +1,8 @@ on: release: - types: [created] + types: [ created ] -name: on-creating-new-tag +name: push-changes-on-release jobs: send-pull-requests: @@ -20,8 +20,8 @@ jobs: - name: Create release file run: | - TITLE='${{ github.event.release.name }}' - NOTES='${{ github.event.release.body }}' + TITLE="${{ github.event.release.name }}" + NOTES="${{ github.event.release.body }}" DATE=$(date '+%d-%m-%Y') mkdir -p deployments echo $NOTES >> "deployments/release-$TITLE-$DATE.txt" @@ -35,7 +35,7 @@ jobs: git commit -m "chore: add release file - $TITLE" git push - - name: Checkout to other repo + - name: Checkout to curve-js uses: actions/checkout@v3 with: repository: curvefi/curve-js @@ -45,7 +45,7 @@ jobs: run: | TAG=${{ env.RELEASE_VERSION }} ORG="curvefi" - CURRENT_REPOSITORY="$ORG/stableswap-ng" + CURRENT_REPOSITORY="${{ github.repository }}" REPOSITORY="$ORG/curve-js" FOLDER="bin/$REPOSITORY" BRANCH_NAME="deployment-$CURRENT_REPOSITORY-$TAG" @@ -68,7 +68,45 @@ jobs: # create a pull-requests containing the updates. gh auth login --with-token < token.txt gh pr create \ - --body "Link to release: https://github.com/$CURRENT_REPOSITORY/releases/tag/$TAG" \ + --body "${{ github.event.release.body }}" \ --title "Deployment from $CURRENT_REPOSITORY - $TAG" \ --head "$BRANCH_NAME" \ --base "master" + + - name: Checkout to curve-api + uses: actions/checkout@v3 + with: + repository: curvefi/curve-api + token: ${{ secrets.ACCESS_TOKEN }} + + - name: Send pull-request to curve-api + run: | + TAG=${{ env.RELEASE_VERSION }} + ORG="curvefi" + CURRENT_REPOSITORY="${{ github.repository }}" + REPOSITORY="$ORG/curve-api" + FOLDER="bin/$REPOSITORY" + BRANCH_NAME="deployment-$CURRENT_REPOSITORY-$TAG" + + # Setup the committers identity. + git config user.email "Curvefi@users.noreply.github.com" + git config user.name "Curvefi" + + # Create a new feature branch for the changes. + git checkout -b $BRANCH_NAME + + # Store the PAT in a file that can be accessed by the GitHub CLI. + echo "${{ secrets.ACCESS_TOKEN }}" > token.txt + + # Create an empty commit for PR + git commit --allow-empty -m "deployment from $CURRENT_REPOSITORY - $TAG" + git push -u origin $BRANCH_NAME + + # Authorize GitHub CLI for the current repository and + # create a pull-requests containing the updates. + gh auth login --with-token < token.txt + gh pr create \ + --body "${{ github.event.release.body }}" \ + --title "Deployment from $CURRENT_REPOSITORY - $TAG" \ + --head "$BRANCH_NAME" \ + --base "main" From 54bc61409ef3c439c2c35bce518100f0137ec05b Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:58:24 +0200 Subject: [PATCH 324/337] deployed to xlayer --- scripts/deploy_infra.py | 17 +++++++++++++++-- scripts/deploy_proxy_admin.py | 6 +++++- scripts/deployment_utils.py | 4 ++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 419ec5cf..d4e31172 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -72,7 +72,7 @@ }, "pzkevm:mainnet": { "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", @@ -160,6 +160,14 @@ "zap": "", "factory_ctor": "000000000000000000000000f3a431008396df8a8b2df492c913706bdb0874ef0000000000000000000000002d12d0907a388811e3aa855a550f959501d303ee", # noqa:E501 }, + "xlayer:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + "views": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + }, } @@ -244,6 +252,7 @@ def deploy_infra(network, url, account, fork=False): factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) # zap: + zap_contract_obj = set_evm_version("./contracts/main/MetaZapNG.vy", network) check_and_deploy(zap_contract_obj, "zap", network) # Set up AMM implementations: @@ -276,7 +285,11 @@ def deploy_infra(network, url, account, fork=False): def main(): - deploy_infra(":mainnet", os.environ["RPC_"], "", fork=False) + deployer = "FIDDYDEPLOYER" + fork = False + network = "xlayer" + rpc = "https://xlayerrpc.okx.com" + deploy_infra(f"{network}:mainnet", rpc, deployer, fork=fork) if __name__ == "__main__": diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index fca78790..6fb14c58 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -34,7 +34,11 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): - deploy_proxy_admin(":mainnet", os.environ["RPC_"], "", fork=False) + network = "xlayer" + rpc = "https://xlayerrpc.okx.com" + account = "FIDDYDEPLOYER" + fork = False + deploy_proxy_admin(network, rpc, account, fork) if __name__ == "__main__": diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index 188326af..ece9aee5 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -106,6 +106,10 @@ class CurveNetworkSettings: dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), + "xlayer:mainnet": CurveNetworkSettings( + dao_ownership_contract="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", + ), } From 98c8ee9e8a57699709c6544438b3de659ade903e Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:49:17 +0200 Subject: [PATCH 325/337] fix workflow --- .github/workflows/push_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push_pr.yml b/.github/workflows/push_pr.yml index a3656705..1ac7c17a 100644 --- a/.github/workflows/push_pr.yml +++ b/.github/workflows/push_pr.yml @@ -20,8 +20,8 @@ jobs: - name: Create release file run: | - TITLE="${{ github.event.release.name }}" - NOTES="${{ github.event.release.body }}" + TITLE='${{ github.event.release.name }}' + NOTES='${{ github.event.release.body }}' DATE=$(date '+%d-%m-%Y') mkdir -p deployments echo $NOTES >> "deployments/release-$TITLE-$DATE.txt" From 12a0c7df1fc490ff8e5a977a0cbadf86f1351c8f Mon Sep 17 00:00:00 2001 From: curvefi Date: Wed, 1 May 2024 11:47:20 +0000 Subject: [PATCH 326/337] chore: add release file - XLayer --- deployments/release-XLayer-01-05-2024.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 deployments/release-XLayer-01-05-2024.txt diff --git a/deployments/release-XLayer-01-05-2024.txt b/deployments/release-XLayer-01-05-2024.txt new file mode 100644 index 00000000..29fc260d --- /dev/null +++ b/deployments/release-XLayer-01-05-2024.txt @@ -0,0 +1 @@ +math: 0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6 views: 0xd7E72f3615aa65b92A4DBdC211E296a35512988B plain_amm: 0x87FE17697D0f14A222e8bEf386a0860eCffDD617 meta_amm: 0x1764ee18e8B3ccA4787249Ceb249356192594585 factory: 0x5eeE3091f747E60a045a2E715a4c71e600e31F6E zap: 0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22 From c97bdcdd9b4ae36bd6d2b09f2dc9f69e772ae067 Mon Sep 17 00:00:00 2001 From: curvefi Date: Fri, 31 May 2024 14:40:33 +0000 Subject: [PATCH 327/337] chore: add release file - Mantle --- deployments/release-Mantle-31-05-2024.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 deployments/release-Mantle-31-05-2024.txt diff --git a/deployments/release-Mantle-31-05-2024.txt b/deployments/release-Mantle-31-05-2024.txt new file mode 100644 index 00000000..58d80a46 --- /dev/null +++ b/deployments/release-Mantle-31-05-2024.txt @@ -0,0 +1 @@ +math: 0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6 views: 0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5 plain_amm: 0x87FE17697D0f14A222e8bEf386a0860eCffDD617 meta_amm: 0x1764ee18e8B3ccA4787249Ceb249356192594585 factory: 0x5eeE3091f747E60a045a2E715a4c71e600e31F6E From 6ac9378c0f9e1bfc9cd3eecb0a68cca133beb82d Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:47:52 +0200 Subject: [PATCH 328/337] testing zksync deployment: --- poetry.lock | 1682 +++++++++++++++++++-------------- pyproject.toml | 6 +- scripts/deploy_infra.py | 117 ++- scripts/deploy_proxy_admin.py | 30 +- scripts/deployment_utils.py | 3 +- 5 files changed, 1097 insertions(+), 741 deletions(-) diff --git a/poetry.lock b/poetry.lock index 58e38c0f..315a6f9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "asttokens" version = "2.4.1" @@ -269,48 +280,48 @@ files = [ [[package]] name = "cbor2" -version = "5.6.0" +version = "5.6.3" description = "CBOR (de)serializer with extensive tag support" optional = false python-versions = ">=3.8" files = [ - {file = "cbor2-5.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7569627514699b10d903795e344e5520cd758f7db968e46e667b6875c409610b"}, - {file = "cbor2-5.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e6d5c5b5cb25450561c92c9ac7d72912027cfa8807aab77ea6f5549e2157335"}, - {file = "cbor2-5.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19403d65c32709c4940ae729470dd77ed88ebbd4972355d0ec23ff4dbe34faa3"}, - {file = "cbor2-5.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329196be76fbba0ad63926913eb225dddcfc4891ff6760484fdcf071cebc7188"}, - {file = "cbor2-5.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac835523af0e37086b5f6aac9283e45cffdf6fa0f1be5eecf967014e7184d948"}, - {file = "cbor2-5.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ab6734098c494687b26531ed4d44c06f287a44061ae1bc16b3fa65563d80b0"}, - {file = "cbor2-5.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:777897d46e31bc2683ed9e015a9c97fc97929bf78d620fc3361fe39a86912eba"}, - {file = "cbor2-5.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5d3932c3f0637439f121a54b9e6020e62e9f2620751b2e850a6f09f1c1ee299e"}, - {file = "cbor2-5.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f017d451b9b7e45759e9ffbfae8a2b1bda3f6a547d6451f7761655a8438f956"}, - {file = "cbor2-5.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c66cf65766195c310b2134ceeb20927fce85373d13483e97d2211dd499739f9b"}, - {file = "cbor2-5.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2411ad0fc8817ff38076523bfc43d193188eedcb0d3a1c52428672636f3760d4"}, - {file = "cbor2-5.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b5ce2c7c9172c401db047202029f96a5799c68ff0c936874c596e3718cd383e4"}, - {file = "cbor2-5.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a4e1bb41ac5dc27bd4737c92934e9daf38440ce849eaf3e25da24f9d1fd2195"}, - {file = "cbor2-5.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:83736e076e878f3679ef2dd6cfce5ebcb71f65f9eeada1d14b16b5b87dbc2250"}, - {file = "cbor2-5.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8724f172b581414444801bd7e4974a1512822231e30162c7d5a6374a3c89ddbf"}, - {file = "cbor2-5.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ae83ca762a60b43c83c23edf487e41dc90ba7cc98e25134cde2bf09e99ec1fa3"}, - {file = "cbor2-5.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da8f4aed346ff0ce667254331d341ad27d8d62c8813536f019d8a68aef40eba"}, - {file = "cbor2-5.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99780a5fe8e23a467eb1084feb74b9bdc6598f0eb6c09821b00bb65ddd834d67"}, - {file = "cbor2-5.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0167b4fd537db4925ee8c2890fb950493280628d9c18034625fa5c8a96db689"}, - {file = "cbor2-5.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a073761d9148e4ffbfb0e4125643c059a4d5d5c5c5448cd56759765e1113487"}, - {file = "cbor2-5.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:62ced11efb37729e1a2a5f04fb8c2661d46ae6adbdddc23cb31ffad2ec75ff7a"}, - {file = "cbor2-5.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0130d50aa1cd0ba8ce65eda5bbbb57bda3f2c9cd86bba7d8be5e3c9b19f88f93"}, - {file = "cbor2-5.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:29ba57a33fcffd5d70fd6ac1182520e887918f9d2b6225a06eaae029f070e18f"}, - {file = "cbor2-5.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe7a459aae6ca4abdc490fdf89463cbeea3449569eca853af7c2672286edcea0"}, - {file = "cbor2-5.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0c098a2802ec1df0a6e96c415203c8a52c42158e1e07d8074c9acf19bcc146f"}, - {file = "cbor2-5.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3184d91a855aedb6f0e76e5ac548a1e43a7fcd3d5ba2deb0894b81c77c2c461"}, - {file = "cbor2-5.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:622b4fe945ecdd93df8c21169c8f1a85dcf21d78ecb1e8b7f9f2795520480010"}, - {file = "cbor2-5.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:aaf6a424742f55e2991c7013df02a629d24c64e5730c3fd3aebcbbebc580bfab"}, - {file = "cbor2-5.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e1ff25fb1b6bafbdb192037860471962f59ce9c1f584611572360e15725abbb1"}, - {file = "cbor2-5.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4143152579ff9e0668688d6d41e2f6a20161f8dc5af97b1b92f400d3112888af"}, - {file = "cbor2-5.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7b7cf96e9027138129edbfa0e2024d6f4beb7db42c2a992ab867eff3c04d46"}, - {file = "cbor2-5.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789496ece76712c298f1fd4681d074f6828ed0d788076c5949c4474c11291f68"}, - {file = "cbor2-5.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3d8525d4f525add7971b75b7ff947eed085405d9db7ad6fad456909cbffd18c1"}, - {file = "cbor2-5.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c9423aaf110ce7c3e856f2d5dc6ece7358de48bad7a88d2633f5943d5cd20676"}, - {file = "cbor2-5.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:52fa913133b82578244e6b86b8dff9d88c9d13acf84955b6942650689f302c01"}, - {file = "cbor2-5.6.0-py3-none-any.whl", hash = "sha256:7ba2d0abbb199dd08b7ea59038dbf6524277f124d836b66744cfdd49d31fd012"}, - {file = "cbor2-5.6.0.tar.gz", hash = "sha256:9d94e2226f8f5792fdba5ab20e07b9bfe02e76c10c3ca126418cd4310439d002"}, + {file = "cbor2-5.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0201d5e8d9ad1557aeb50d35b907c0f170de0ae9ebb484b2894bcee3b2e13b80"}, + {file = "cbor2-5.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eda6965cca276d4c2cebdbee14572dec65b991c5359fc32a793f03f052e35985"}, + {file = "cbor2-5.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14561038b8eaab3fd5e867f09bc43f7525a1405e41ade14066925ea3d42513a8"}, + {file = "cbor2-5.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a3cf6b339a005031e4b8c79b9541856e3b0077ea4c33d7bb6a019885136f53a"}, + {file = "cbor2-5.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4b7636d39de203ee30ac13575ed3e9a0510e993fa1671022b84b9e35e369825f"}, + {file = "cbor2-5.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23847075ce1bcda871c7698e5db0635685995ae470098a5e4c9a26c00f65f21a"}, + {file = "cbor2-5.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ca15be7142e861fb9f918e0248620b4d4153b9ff14ef6034f7204db5db2924a1"}, + {file = "cbor2-5.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b07ee755ae5b0dfad608dab37364b35895cab5d1222653da1fea32a10330c4b0"}, + {file = "cbor2-5.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fc063843c14e9e95181faf8d807a53c958d77bb9d360eb4f2344d075ecfed36"}, + {file = "cbor2-5.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c66d4c227c2ed6c63ec5c2d50eb8ec0e1c41c07b452a867544e48ca41d4f0b64"}, + {file = "cbor2-5.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3af60ac82a733bfdfb2b1079c850fefea2621bdb8c8f87f4c5d12802d48a8c55"}, + {file = "cbor2-5.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:acb93292843aa72768f089a135bfeec4c9b745132e8dc22f1b149490fc77cb0a"}, + {file = "cbor2-5.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193d1abdffd52893710d39389daa5c03e1569421cdf53585a28033689aef7aec"}, + {file = "cbor2-5.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:d0c915db92b441f505f8a14a521c9461439ac8e5d959454845eb92f93db0bb3b"}, + {file = "cbor2-5.6.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9eaec8c04618124a6b597fe4471035cb7cb0d5114f43aaf2062821ad480ef57c"}, + {file = "cbor2-5.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d1e5181d4f858237ab4e1a28e21bdcaf31dab2657ab60a8d4a0701a078fe5926"}, + {file = "cbor2-5.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:277997127402710a3abdf4372ac75e8f8bb2e75a303cd789312e515c8ef657dd"}, + {file = "cbor2-5.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:add01e4b4663199940d10f8c8e1d926e70823d1b2f3f981cc097a4764125f110"}, + {file = "cbor2-5.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:adc87485ffd7a4dad481e08e6819eebfcfbafc0918fffcca47aee4cdf8c6de04"}, + {file = "cbor2-5.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea4a0412426155c3b78763449db56cf5c72c48788a37d7e60bd66c844b9c8634"}, + {file = "cbor2-5.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:18b3dee4eddde9761c60298ce21c0cd4e770237978034c5ee1d4242e255683ec"}, + {file = "cbor2-5.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ff6fd1c54b97ee322c0b7180092305ca3b012ff78fddadad97b33490f5f8881f"}, + {file = "cbor2-5.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ac6f10b9d25f2d61c036f86238bf23e3ea0253f98faa8ab00f67228bf3c0ce2a"}, + {file = "cbor2-5.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be74f2cbda547fdd57c83ee5b3470804f02c660db28efcf9d4016f001b66f40"}, + {file = "cbor2-5.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64ea120206f82492a4385bbc5e2639f9b67c8bc7bdc57bffcbe9a8fee8cd6342"}, + {file = "cbor2-5.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c3d2902e1aed155d56cdcae99cd4a9dae843e3fff6978148d2d5d5f9a0b986cd"}, + {file = "cbor2-5.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d4f95a567e26d8d9d62db234cd089525c52f19e7fdd59152629d9f03bd94b4f"}, + {file = "cbor2-5.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:33efbe7103bac090430d291fca2fe1c444b0ec55c4716e8051b72a81377e8b79"}, + {file = "cbor2-5.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:81e619a2a59ae966cedb5fd3ea8a9487a3d4430824bbeacdcf5f74ad6112cc57"}, + {file = "cbor2-5.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b7755b93d32638f4d79a0fa0744b423787f6faa3c96ccccac68b6dbf1848368"}, + {file = "cbor2-5.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f0e95011ae8460265ef348fe380664fa22c51015fd52344ebd781579fa9552a"}, + {file = "cbor2-5.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7693e53c3ba0b2ad4e46b610f8d69159ffdbcb6ebe75ea1c1f5f40c3283639ca"}, + {file = "cbor2-5.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e3ec251db32516d383fc587874b15f4b5fb4e9049d9436b8696f5767b11c149b"}, + {file = "cbor2-5.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6081c1ab9791d5973a40b95ecb8b04b0fbf9fc04be170d89a3ad77d5964f52d5"}, + {file = "cbor2-5.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:2aba8b75e36c9f84a42a7026271da8fd759035a871c1b799028439059527276b"}, + {file = "cbor2-5.6.3-py3-none-any.whl", hash = "sha256:8a4b7404af6da719092a4ee5953d1930d095b93b684bf99e1ab74512be1910a4"}, + {file = "cbor2-5.6.3.tar.gz", hash = "sha256:e6f0ae2751c2d333a960e0807c0611494eb1245631a167965acbc100509455d3"}, ] [package.extras] @@ -320,13 +331,13 @@ test = ["coverage (>=7)", "hypothesis", "pytest"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -503,6 +514,100 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "ckzg" +version = "1.0.2" +description = "Python bindings for C-KZG-4844" +optional = false +python-versions = "*" +files = [ + {file = "ckzg-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdd082bc0f2a595e3546658ecbe1ff78fe65b0ab7e619a8197a62d94f46b5b46"}, + {file = "ckzg-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50ca4af4e2f1a1e8b0a7e97b3aef39dedbb0d52d90866ece424f13f8df1b5972"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e9dc671b0a307ea65d0a216ca496c272dd3c1ed890ddc2a306da49b0d8ffc83"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d95e97a0d0f7758119bb905fb5688222b1556de465035614883c42fe4a047d1f"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27261672154cbd477d84d289845b0022fbdbe2ba45b7a2a2051c345fa04c8334"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c16d5ee1ddbbbad0367ff970b3ec9f6d1879e9f928023beda59ae9e16ad99e4c"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:09043738b029bdf4fdc82041b395cfc6f5b5cf63435e5d4d685d24fd14c834d3"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3c0afa232d2312e3101aaddb6971b486b0038a0f9171500bc23143f5749eff55"}, + {file = "ckzg-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:96e8281b6d58cf91b9559e1bd38132161d63467500838753364c68e825df2e2c"}, + {file = "ckzg-1.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b874167de1d6de72890a2ad5bd9aa7adbddc41c3409923b59cf4ef27f83f79da"}, + {file = "ckzg-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d2ccd68b0743e20e853e31a08da490a8d38c7f12b9a0c4ee63ef5afa0dc2427"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e8d534ddbe785c44cf1cd62ee32d78b4310d66dd70e42851f5468af655b81f5"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c732cda00c76b326f39ae97edfc6773dd231b7c77288b38282584a7aee77c3a7"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc5a27284db479ead4c053ff086d6e222914f1b0aa08b80eabfa116dbed4f7a"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6bd5006cb3e802744309450183087a6594d50554814eee19065f7064dff7b05"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3594470134eda7adf2813ad3f1da55ced98c8a393262f47ce3890c5afa05b23e"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fea56f39e48b60c1ff6f751c47489e353d1bd95cae65c429cf5f87735d794431"}, + {file = "ckzg-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f769eb2e1056ca396462460079f6849c778f58884bb24b638ff7028dd2120b65"}, + {file = "ckzg-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e3cb2f8c767aee57e88944f90848e8689ce43993b9ff21589cfb97a562208fe7"}, + {file = "ckzg-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b29889f5bc5db530f766871c0ff4133e7270ecf63aaa3ca756d3b2731980802"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfcc70fb76b3d36125d646110d5001f2aa89c1c09ff5537a4550cdb7951f44d4"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ca8a256cdd56d06bc5ef24caac64845240dbabca402c5a1966d519b2514b4ec"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ea91b0236384f93ad1df01d530672f09e254bd8c3cf097ebf486aebb97f6c8c"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65311e72780105f239d1d66512629a9f468b7c9f2609b8567fc68963ac638ef9"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d7600ce7a73ac41d348712d0c1fe5e4cb6caa329377064cfa3a6fd8fbffb410"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19893ee7bd7da8688382cb134cb9ee7bce5c38e3a9386e3ed99bb010487d2d17"}, + {file = "ckzg-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:c3e1a9a72695e777497e95bb2213316a1138f82d1bb5d67b9c029a522d24908e"}, + {file = "ckzg-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2f59da9cb82b6a4be615f2561a255731eededa7ecd6ba4b2f2dedfc918ef137"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c915e1f2ef51657c3255d8b1e2aea6e0b93348ae316b2b79eaadfb17ad8f514e"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcc0d2031fcabc4be37e9e602c926ef9347238d2f58c1b07e0c147f60b9e760b"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cdaad2745425d7708e76e8e56a52fdaf5c5cc1cfefd5129d24ff8dbe06a012d"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1ec775649daade1b93041aac9c1660c2ad9828b57ccd2eeb5a3074d8f05e544a"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:02f9cc3e38b3702ec5895a1ebf927fd02b8f5c2f93c7cb9e438581b5b74472c8"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0e816af31951b5e94e6bc069f21fe783427c190526e0437e16c4488a34ddcacc"}, + {file = "ckzg-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:651ba33ee2d7fefff14ca519a72996b733402f8b043fbfef12d5fe2a442d86d8"}, + {file = "ckzg-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:489763ad92e2175fb6ab455411f03ec104c630470d483e11578bf2e00608f283"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e1376284e9a5094d7c4d3e552202d6b32a67c5acc461b0b35718d8ec5c7363"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb9d0b09ca1bdb5955b626d6645f811424ae0fcab47699a1a938a3ce0438c25f"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87a121ace8feb6c9386f247e7e36ef55e584fc8a6b1bc2c60757a59c1efe364"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:97c27153fab853f017fed159333b27beeb2e0da834c92c9ecdc26d0e5c3983b3"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b26799907257c39471cb3665f66f7630797140131606085c2c94a7094ab6ddf2"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:283a40c625222560fda3dcb912b666f7d50f9502587b73c4358979f519f1c961"}, + {file = "ckzg-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5f029822d27c52b9c3dbe5706408b099da779f10929be0422a09a34aa026a872"}, + {file = "ckzg-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edaea8fb50b01c6c19768d9305ad365639a8cd804754277d5108dcae4808f00b"}, + {file = "ckzg-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27be65c88d5d773a30e6f198719cefede7e25cad807384c3d65a09c11616fc9d"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9ac729c5c6f3d2c030c0bc8c9e10edc253e36f002cfe227292035009965d349"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1528bc2b95aac6d184a90b023602c40d7b11b577235848c1b5593c00cf51d37"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:071dc7fc179316ce1bfabaa056156e4e84f312c4560ab7b9529a3b9a84019df3"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:895044069de7010be6c7ee703f03fd7548267a0823cf60b9dd26ec50267dd9e8"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ed8c99cd3d9af596470e0481fd58931007288951719bad026f0dd486dd0ec11"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74d87eafe561d4bfb544a4f3419d26c56ad7de00f39789ef0fdb09515544d12e"}, + {file = "ckzg-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:54d71e5ca416bd51c543f9f51e426e6792f8a0280b83aef92faad1b826f401ea"}, + {file = "ckzg-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da2d9988781a09a4577ee7ea8f51fe4a94b4422789a523164f5ba3118566ad41"}, + {file = "ckzg-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d9e030af7d6acdcb356fddfb095048bc8e880fe4cd70ff2206c64f33bf384a0d"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:145ae31c3d499d1950567bd636dc5b24292b600296b9deb5523bc20d8f7b51c3"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d81e68e84d80084da298471ad5eaddfcc1cf73545cb24e9453550c8186870982"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67064bbbeba1a6892c9c80b3d0c2a540ff48a5ca5356fdb2a8d998b264e43e6"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:99694917eb6decefc0d330d9887a89ea770824b2fa76eb830bab5fe57ea5c20c"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fca227ce0ce3427254a113fdb3aed5ecd99c1fc670cb0c60cc8a2154793678e4"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a66a690d3d1801085d11de6825df47a99b465ff32dbe90be4a3c9f43c577da96"}, + {file = "ckzg-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:272adfe471380d10e4a0e1639d877e504555079a60233dd82249c799b15be81e"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f37be0054ebb4b8ac6e6d5267290b239b09e7ddc611776051b4c3c4032d161ba"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:611c03a170f0f746180eeb0cc28cdc6f954561b8eb9013605a046de86520ee6b"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b2f0ab341f3c33702ce64e1c101116c7462a25686d0b1a0193ca654ad4f96e"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab29fc61fbd32096b82b02e6b18ae0d7423048d3540b7b90805b16ae10bdb769"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43741e7453262aa3ba1754623d7864250b33751bd850dd548e3ed6bd1911093"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:155eacc237cb28c9eafda1c47a89e6e4550f1c2e711f2eee21e0bb2f4df75546"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31d7fbe396a51f43375e38c31bc3a96c7996882582f95f3fcfd54acfa7b3ce6"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d3d049186c9966e9140de39a9979d7adcfe22f8b02d2852c94d3c363235cc18"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88728fbd410d61bd5d655ac50b842714c38bc34ff717f73592132d28911fc88e"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:052d302058d72431acc9dd4a9c76854c8dfce10c698deef5252884e32a1ac7bf"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:633110a9431231664be2ad32baf10971547f18289d33967654581b9ae9c94a7e"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f439c9e5297ae29a700f6d55de1525e2e295dbbb7366f0974c8702fca9e536b9"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:94f7eb080c00c0ccbd4fafad69f0b35b624a6a229a28e11d365b60b58a072832"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f876783ec654b7b9525503c2a0a1b086e5d4f52ff65cac7e8747769b0c2e5468"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7e039800e50592580171830e788ef4a1d6bb54300d074ae9f9119e92aefc568"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a8cccf0070a29bc01493179db2e61220ee1a6cb17f8ea41c68a2f043ace87f"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f86cef801d7b0838e17b6ee2f2c9e747447d91ad1220a701baccdf7ef11a3c8"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2433a89af4158beddebbdd66fae95b34d40f2467bee8dc40df0333de5e616b5f"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c49d5dc0918ad912777720035f9820bdbb6c7e7d1898e12506d44ab3c938d525"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:331d49bc72430a3f85ea6ecb55a0d0d65f66a21d61af5783b465906a741366d5"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86627bc33bc63b8de869d7d5bfa9868619a4f3e4e7082103935c52f56c66b5"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab6a2ba2706b5eaa1ce6bc7c4e72970bf9587e2e0e482e5fb4df1996bccb7a40"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8bca5e7c38d913fabc24ad09545f78ba23cfc13e1ac8250644231729ca908549"}, + {file = "ckzg-1.0.2.tar.gz", hash = "sha256:4295acc380f8d42ebea4a4a0a68c424a322bb335a33bad05c72ead8cbb28d118"}, +] + [[package]] name = "cleo" version = "2.1.0" @@ -543,6 +648,73 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.5.3" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "crashtest" version = "0.4.1" @@ -556,43 +728,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.2" +version = "42.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, - {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, - {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, - {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, - {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, - {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, - {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, + {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, + {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, + {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, + {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, + {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, + {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, ] [package.dependencies] @@ -849,90 +1021,94 @@ pgp = ["gpg"] [[package]] name = "eip712" -version = "0.2.2" +version = "0.1.dev42+geed3f8d" description = "eip712: Message classes for typed structured data hashing and signing in Ethereum" optional = false python-versions = ">=3.8,<4" -files = [ - {file = "eip712-0.2.2-py3-none-any.whl", hash = "sha256:576476dd1d276e444a633ac22ab25209e18f8f41e5016e576a132d190043a4ba"}, - {file = "eip712-0.2.2.tar.gz", hash = "sha256:6d2e07a83c66fb1cbe2448bb4dfea1c91913c4822b7d9b89231e5b61473ae426"}, -] +files = [] +develop = false [package.dependencies] dataclassy = ">=0.8.2,<1" -eth-abi = ">=4.1.0,<5" -eth-account = ">=0.8.0,<0.9" +eth-abi = ">=5.1.0,<6" +eth-account = ">=0.12.0,<0.14" eth-hash = {version = "*", extras = ["pycryptodome"]} -eth-typing = ">=3.3.0,<4" -eth-utils = ">=2.1.0,<3" -hexbytes = ">=0.3.0,<1" +eth-typing = ">=3.5.2,<4" +eth-utils = ">=2.3.1,<3" +hexbytes = ">=1.2.0,<2" [package.extras] -dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.7.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.0.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.5.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] -doc = ["Sphinx (>=5.3.0,<6)", "myst-parser (>=0.18.1,<0.19)", "sphinx-rtd-theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] -lint = ["black (>=23.7.0,<24)", "flake8 (>=6.0.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.16,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.5.1,<2)", "types-setuptools"] +dev = ["IPython", "Sphinx (>=5.3.0,<6)", "black (>=23.12.0,<24)", "commitizen (>=2.42,<3)", "flake8 (>=6.1.0,<7)", "hypothesis (>=6.70.0,<7)", "ipdb", "isort (>=5.12.0,<6)", "mdformat (>=0.7.17,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.7.1,<2)", "myst-parser (>=0.18.1,<0.19)", "pre-commit", "pytest (>=6.0,<8)", "pytest-cov", "pytest-watch", "pytest-xdist", "setuptools", "sphinx_rtd_theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)", "twine", "types-setuptools", "wheel"] +doc = ["Sphinx (>=5.3.0,<6)", "myst-parser (>=0.18.1,<0.19)", "sphinx_rtd_theme (>=1.2.0,<2)", "sphinxcontrib-napoleon (>=0.7)"] +lint = ["black (>=23.12.0,<24)", "flake8 (>=6.1.0,<7)", "isort (>=5.12.0,<6)", "mdformat (>=0.7.17,<0.8)", "mdformat-frontmatter (>=0.4.1,<0.5)", "mdformat-gfm (>=0.3.5,<0.4)", "mypy (>=1.7.1,<2)", "types-setuptools"] release = ["setuptools", "twine", "wheel"] test = ["hypothesis (>=6.70.0,<7)", "pytest (>=6.0,<8)", "pytest-cov", "pytest-xdist"] +[package.source] +type = "git" +url = "https://github.com/DanielSchiavini/eip712.git" +reference = "main" +resolved_reference = "eed3f8ddbde976f1107d259fd262d2bd687dea39" + [[package]] name = "eth-abi" -version = "4.2.1" +version = "5.1.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" optional = false -python-versions = ">=3.7.2, <4" +python-versions = "<4,>=3.8" files = [ - {file = "eth_abi-4.2.1-py3-none-any.whl", hash = "sha256:abd83410a5326145bf178675c276de0ed154f6dc695dcad1beafaa44d97f44ae"}, - {file = "eth_abi-4.2.1.tar.gz", hash = "sha256:60d88788d53725794cdb07c0f0bb0df2a31a6e1ad19644313fe6117ac24eeeb0"}, + {file = "eth_abi-5.1.0-py3-none-any.whl", hash = "sha256:84cac2626a7db8b7d9ebe62b0fdca676ab1014cc7f777189e3c0cd721a4c16d8"}, + {file = "eth_abi-5.1.0.tar.gz", hash = "sha256:33ddd756206e90f7ddff1330cc8cac4aa411a824fe779314a0a52abea2c8fc14"}, ] [package.dependencies] eth-typing = ">=3.0.0" eth-utils = ">=2.0.0" -parsimonious = ">=0.9.0,<0.10.0" +parsimonious = ">=0.10.0,<0.11.0" [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] -test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)"] tools = ["hypothesis (>=4.18.2,<5.0.0)"] [[package]] name = "eth-account" -version = "0.8.0" +version = "0.13.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" optional = false -python-versions = ">=3.6, <4" +python-versions = "<4,>=3.8" files = [ - {file = "eth-account-0.8.0.tar.gz", hash = "sha256:ccb2d90a16c81c8ea4ca4dc76a70b50f1d63cea6aff3c5a5eddedf9e45143eca"}, - {file = "eth_account-0.8.0-py3-none-any.whl", hash = "sha256:0ccc0edbb17021004356ae6e37887528b6e59e6ae6283f3917b9759a5887203b"}, + {file = "eth_account-0.13.0-py3-none-any.whl", hash = "sha256:84d27664038c68e6bee28fa5de803c3b629dc5d97cd61d12e5f442c561a5b8af"}, + {file = "eth_account-0.13.0.tar.gz", hash = "sha256:ccea4383d9d37b46e17c977b0a5ea397cef1ac1ad3330431ae4b0c10b62d4fcd"}, ] [package.dependencies] -bitarray = ">=2.4.0,<3" -eth-abi = ">=3.0.1" -eth-keyfile = ">=0.6.0,<0.7.0" -eth-keys = ">=0.4.0,<0.5" -eth-rlp = ">=0.3.0,<1" -eth-utils = ">=2.0.0,<3" -hexbytes = ">=0.1.0,<1" -rlp = ">=1.0.0,<4" +bitarray = ">=2.4.0" +ckzg = ">=0.4.3" +eth-abi = ">=4.0.0-b.2" +eth-keyfile = ">=0.6.0" +eth-keys = ">=0.4.0" +eth-rlp = ">=2.1.0" +eth-utils = ">=2.0.0" +hexbytes = ">=1.2.0" +pydantic = ">=2.0.0" +rlp = ">=1.0.0" [package.extras] -dev = ["Sphinx (>=1.6.5,<5)", "black (>=22,<23)", "bumpversion (>=0.5.3,<1)", "coverage", "flake8 (==3.7.9)", "hypothesis (>=4.18.0,<5)", "ipython", "isort (>=4.2.15,<5)", "jinja2 (>=3.0.0,<3.1.0)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)", "tox (==3.25.0)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<5)", "jinja2 (>=3.0.0,<3.1.0)", "sphinx-rtd-theme (>=0.1.9,<1)", "towncrier (>=21,<22)"] -lint = ["black (>=22,<23)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.910)", "pydocstyle (>=5.0.0,<6)"] -test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.25.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "coverage", "hypothesis (>=4.18.0,<5)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-bloom" -version = "3.0.0" +version = "3.0.1" description = "A python implementation of the bloom filter used by Ethereum" optional = false -python-versions = ">=3.8, <4" +python-versions = "<4,>=3.8" files = [ - {file = "eth-bloom-3.0.0.tar.gz", hash = "sha256:94bab384b01f2eb1012abbd6bb504e4c743878414d8695ee5a5d25f4247b3886"}, - {file = "eth_bloom-3.0.0-py3-none-any.whl", hash = "sha256:bb884ece93d292dfbbe4696744db874a88ac5bfc45f6f1b0ee147d801604a46c"}, + {file = "eth_bloom-3.0.1-py3-none-any.whl", hash = "sha256:c1eb51cb9f9ec8834b691d67e73c02c4e79e22c81ae8058209971803236ffbb2"}, + {file = "eth_bloom-3.0.1.tar.gz", hash = "sha256:6be3dfa44a597a0bc8d974c381fb9a60bbcadfb56e88e69ab55ba538d90b3256"}, ] [package.dependencies] @@ -945,13 +1121,13 @@ test = ["hypothesis (>=3.31.2)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-hash" -version = "0.6.0" +version = "0.7.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" optional = false python-versions = ">=3.8, <4" files = [ - {file = "eth-hash-0.6.0.tar.gz", hash = "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478"}, - {file = "eth_hash-0.6.0-py3-none-any.whl", hash = "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3"}, + {file = "eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a"}, + {file = "eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f"}, ] [package.dependencies] @@ -966,69 +1142,67 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keyfile" -version = "0.6.1" -description = "A library for handling the encrypted keyfiles used to store ethereum private keys." +version = "0.8.1" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" optional = false -python-versions = "*" +python-versions = "<4,>=3.8" files = [ - {file = "eth-keyfile-0.6.1.tar.gz", hash = "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c"}, - {file = "eth_keyfile-0.6.1-py3-none-any.whl", hash = "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560"}, + {file = "eth_keyfile-0.8.1-py3-none-any.whl", hash = "sha256:65387378b82fe7e86d7cb9f8d98e6d639142661b2f6f490629da09fddbef6d64"}, + {file = "eth_keyfile-0.8.1.tar.gz", hash = "sha256:9708bc31f386b52cca0969238ff35b1ac72bd7a7186f2a84b86110d3c973bec1"}, ] [package.dependencies] -eth-keys = ">=0.4.0,<0.5.0" -eth-utils = ">=2,<3" +eth-keys = ">=0.4.0" +eth-utils = ">=2" pycryptodome = ">=3.6.6,<4" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "flake8 (==4.0.1)", "idna (==2.7)", "pluggy (>=1.0.0,<2)", "pycryptodome (>=3.6.6,<4)", "pytest (>=6.2.5,<7)", "requests (>=2.20,<3)", "setuptools (>=38.6.0)", "tox (>=2.7.0)", "twine", "wheel"] -keyfile = ["eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "pycryptodome (>=3.6.6,<4)"] -lint = ["flake8 (==4.0.1)"] -test = ["pytest (>=6.2.5,<7)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keys" -version = "0.4.0" -description = "Common API for Ethereum key operations." +version = "0.5.1" +description = "eth-keys: Common API for Ethereum key operations" optional = false -python-versions = "*" +python-versions = "<4,>=3.8" files = [ - {file = "eth-keys-0.4.0.tar.gz", hash = "sha256:7d18887483bc9b8a3fdd8e32ddcb30044b9f08fcb24a380d93b6eee3a5bb3216"}, - {file = "eth_keys-0.4.0-py3-none-any.whl", hash = "sha256:e07915ffb91277803a28a379418bdd1fad1f390c38ad9353a0f189789a440d5d"}, + {file = "eth_keys-0.5.1-py3-none-any.whl", hash = "sha256:ad13d920a2217a49bed3a1a7f54fb0980f53caf86d3bbab2139fd3330a17b97e"}, + {file = "eth_keys-0.5.1.tar.gz", hash = "sha256:2b587e4bbb9ac2195215a7ab0c0fb16042b17d4ec50240ed670bbb8f53da7a48"}, ] [package.dependencies] -eth-typing = ">=3.0.0,<4" -eth-utils = ">=2.0.0,<3.0.0" +eth-typing = ">=3" +eth-utils = ">=2" [package.extras] -coincurve = ["coincurve (>=7.0.0,<16.0.0)"] -dev = ["asn1tools (>=0.146.2,<0.147)", "bumpversion (==0.5.3)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (>=3.0.1,<3.1)", "flake8 (==3.0.4)", "hypothesis (>=5.10.3,<6.0.0)", "mypy (==0.782)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)", "tox (==3.20.0)", "twine"] -eth-keys = ["eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)"] -lint = ["flake8 (==3.0.4)", "mypy (==0.782)"] -test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "factory-boy (>=3.0.1,<3.1)", "hypothesis (>=5.10.3,<6.0.0)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)"] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] [[package]] name = "eth-rlp" -version = "0.3.0" +version = "2.1.0" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-rlp-0.3.0.tar.gz", hash = "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6"}, - {file = "eth_rlp-0.3.0-py3-none-any.whl", hash = "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33"}, + {file = "eth-rlp-2.1.0.tar.gz", hash = "sha256:d5b408a8cd20ed496e8e66d0559560d29bc21cee482f893936a1f05d0dddc4a0"}, + {file = "eth_rlp-2.1.0-py3-none-any.whl", hash = "sha256:6f476eb7e37d81feaba5d98aed887e467be92648778c44b19fe594aea209cde1"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" -hexbytes = ">=0.1.0,<1" -rlp = ">=0.6.0,<4" +eth-utils = ">=2.0.0" +hexbytes = ">=1.2.0" +rlp = ">=0.6.0" +typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.10\""} [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] -lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)"] -test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.14.6)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-stdlib" @@ -1092,13 +1266,13 @@ test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-x [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -1106,13 +1280,13 @@ test = ["pytest (>=6)"] [[package]] name = "execnet" -version = "2.0.2" +version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] [package.extras] @@ -1149,18 +1323,18 @@ pyrepl = ">=0.8.2" [[package]] name = "filelock" -version = "3.13.1" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -1181,20 +1355,19 @@ pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "hexbytes" -version = "0.3.1" +version = "1.2.0" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59"}, - {file = "hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d"}, + {file = "hexbytes-1.2.0-py3-none-any.whl", hash = "sha256:bb243ab58b8d8390e3a753fbc9e3616f0f958df43d874e19ae0e4b746722a7e9"}, + {file = "hexbytes-1.2.0.tar.gz", hash = "sha256:965f1cc712e7b263c41fdf3fb36cf671ba6f59b895937cf33941a5c996ec3a5c"}, ] [package.extras] -dev = ["black (>=22)", "bumpversion (>=0.5.3)", "eth-utils (>=1.0.1,<3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=22)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)"] -test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-utils (>=2.0.0)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-utils (>=2.0.0)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "html5lib" @@ -1219,13 +1392,13 @@ lxml = ["lxml"] [[package]] name = "hypothesis" -version = "6.97.3" +version = "6.103.0" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.97.3-py3-none-any.whl", hash = "sha256:6256d768ec866426bfce6ed78418c6e3e43119a0dbece2e0229a1ae5929ae53d"}, - {file = "hypothesis-6.97.3.tar.gz", hash = "sha256:00216ddadaee17ba73451e262f973970a97d34fd75ec34ef57510147264c34d1"}, + {file = "hypothesis-6.103.0-py3-none-any.whl", hash = "sha256:0d21a87e2d68b4937f19f1e6e681d747de65f748c9caa818308a0e3899ea8481"}, + {file = "hypothesis-6.103.0.tar.gz", hash = "sha256:7fe91917b99fc98ac150ec295775a687448c7c42c2276ab6e4a6969a4b285bb5"}, ] [package.dependencies] @@ -1234,9 +1407,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.4)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.54)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] +crosshair = ["crosshair-tool (>=0.0.54)", "hypothesis-crosshair (>=0.0.4)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -1247,17 +1421,17 @@ pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.4)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2024.1)"] [[package]] name = "identify" -version = "2.5.33" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -1265,33 +1439,33 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.1.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -1317,13 +1491,13 @@ files = [ [[package]] name = "ipython" -version = "8.21.0" +version = "8.24.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.21.0-py3-none-any.whl", hash = "sha256:1050a3ab8473488d7eee163796b02e511d0735cf43a04ba2a8348bd0f2eaf8a5"}, - {file = "ipython-8.21.0.tar.gz", hash = "sha256:48fbc236fbe0e138b88773fa0437751f14c3645fb483f1d4c5dee58b37e5ce73"}, + {file = "ipython-8.24.0-py3-none-any.whl", hash = "sha256:d7bf2f6c4314984e3e02393213bab8703cf163ede39672ce5918c51fe253a2a3"}, + {file = "ipython-8.24.0.tar.gz", hash = "sha256:010db3f8a728a578bb641fdd06c063b9fb8e96a9464c63aec6310fbcb5e80501"}, ] [package.dependencies] @@ -1332,24 +1506,26 @@ decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" -traitlets = ">=5" +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "stack-data", "typing-extensions"] kernel = ["ipykernel"] +matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath", "trio"] +test = ["pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "isort" @@ -1370,13 +1546,13 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jaraco-classes" -version = "3.3.0" +version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, - {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, ] [package.dependencies] @@ -1384,7 +1560,7 @@ more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jedi" @@ -1422,13 +1598,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jsonschema" -version = "4.21.1" +version = "4.22.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, - {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, + {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, + {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, ] [package.dependencies] @@ -1644,13 +1820,13 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "matplotlib-inline" -version = "0.1.6" +version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, - {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, ] [package.dependencies] @@ -1691,67 +1867,67 @@ files = [ [[package]] name = "msgpack" -version = "1.0.7" +version = "1.0.8" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, - {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, - {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, - {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, - {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, - {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, - {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, - {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, - {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, - {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, - {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, - {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] [[package]] @@ -1767,18 +1943,15 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.0" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.0-py2.py3-none-any.whl", hash = "sha256:508ecec98f9f3330b636d4448c0f1a56fc68017c68f1e7857ebc52acf0eb879a"}, + {file = "nodeenv-1.9.0.tar.gz", hash = "sha256:07f144e90dae547bf0d4ee8da0ee42664a42a04e02ed68e06324348dafe4bdb1"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" version = "23.2" @@ -1792,12 +1965,13 @@ files = [ [[package]] name = "parsimonious" -version = "0.9.0" +version = "0.10.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" optional = false python-versions = "*" files = [ - {file = "parsimonious-0.9.0.tar.gz", hash = "sha256:b2ad1ae63a2f65bd78f5e0a8ac510a98f3607a43f1db2a8d46636a5d9e4a30c1"}, + {file = "parsimonious-0.10.0-py3-none-any.whl", hash = "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f"}, + {file = "parsimonious-0.10.0.tar.gz", hash = "sha256:8281600da180ec8ae35427a4ab4f7b82bfec1e3d1e52f80cb60ea82b9512501c"}, ] [package.dependencies] @@ -1805,18 +1979,18 @@ regex = ">=2022.3.15" [[package]] name = "parso" -version = "0.8.3" +version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" files = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] [[package]] name = "pathspec" @@ -1865,17 +2039,17 @@ ptyprocess = ">=0.5" [[package]] name = "pkginfo" -version = "1.9.6" +version = "1.10.0" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.6" files = [ - {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, - {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, + {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, + {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, ] [package.extras] -testing = ["pytest", "pytest-cov"] +testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" @@ -1894,13 +2068,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1975,13 +2149,13 @@ poetry-core = ">=1.6.0,<2.0.0" [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.7.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, ] [package.dependencies] @@ -1993,13 +2167,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.43" +version = "3.0.45" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, - {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, + {file = "prompt_toolkit-3.0.45-py3-none-any.whl", hash = "sha256:a29b89160e494e3ea8622b09fa5897610b437884dcdcd054fdc1308883326c2a"}, + {file = "prompt_toolkit-3.0.45.tar.gz", hash = "sha256:07c60ee4ab7b7e90824b61afa840c8f5aad2d46b3e2e10acc33d8ecc94a49089"}, ] [package.dependencies] @@ -2043,57 +2217,55 @@ files = [ [[package]] name = "py-ecc" -version = "6.0.0" -description = "Elliptic curve crypto in python including secp256k1 and alt_bn128" +version = "7.0.1" +description = "py-ecc: Elliptic curve crypto in python including secp256k1, alt_bn128, and bls12_381" optional = false -python-versions = ">=3.6, <4" +python-versions = "<4,>=3.8" files = [ - {file = "py_ecc-6.0.0-py3-none-any.whl", hash = "sha256:54e8aa4c30374fa62d582c599a99f352c153f2971352171318bd6910a643be0b"}, - {file = "py_ecc-6.0.0.tar.gz", hash = "sha256:3fc8a79e38975e05dc443d25783fd69212a1ca854cc0efef071301a8f7d6ce1d"}, + {file = "py_ecc-7.0.1-py3-none-any.whl", hash = "sha256:84a8b4d436163c83c65345a68e32f921ef6e64374a36f8e561f0455b4b08f5f2"}, + {file = "py_ecc-7.0.1.tar.gz", hash = "sha256:557461f42e57294d734305a30faf6b8903421651871e9cdeff8d8e67c6796c70"}, ] [package.dependencies] -cached-property = ">=1.5.1,<2" -eth-typing = ">=3.0.0,<4" -eth-utils = ">=2.0.0,<3" -mypy-extensions = ">=0.4.1" +cached-property = ">=1.5.1" +eth-typing = ">=3.0.0" +eth-utils = ">=2.0.0" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)", "pytest (==6.2.5)", "pytest-xdist (==1.26.0)", "twine"] -lint = ["flake8 (==3.5.0)", "mypy (==0.641)", "mypy-extensions (>=0.4.1)"] -test = ["pytest (==6.2.5)", "pytest-xdist (==1.26.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "py-evm" -version = "0.8.0b1" +version = "0.10.1b1" description = "Python implementation of the Ethereum Virtual Machine" optional = false -python-versions = "*" +python-versions = "<4,>=3.8" files = [ - {file = "py-evm-0.8.0b1.tar.gz", hash = "sha256:a082eeef14d5189b7f98c76c2b3d75f2bdbe205447e57add27c1c392f5d55544"}, - {file = "py_evm-0.8.0b1-py3-none-any.whl", hash = "sha256:ae22ab813406c248f085caac6d689f3ce8f60ae60e861df6db1618e24c9e503e"}, + {file = "py_evm-0.10.1b1-py3-none-any.whl", hash = "sha256:f0fc4a4b904917b40e6a06f87925017dc48ea6582e95f88d28be38f3566e2bae"}, + {file = "py_evm-0.10.1b1.tar.gz", hash = "sha256:aeb889514af12b6a8cb5091fe93008642eadf7c19999859dad3191eaf451647c"}, ] [package.dependencies] -cached-property = ">=1.5.1,<2" +cached-property = ">=1.5.1" +ckzg = ">=0.4.3" eth-bloom = ">=1.0.3" -eth-keys = ">=0.4.0,<0.5.0" -eth-typing = ">=3.3.0,<4.0.0" -eth-utils = ">=2.0.0,<3.0.0" +eth-keys = ">=0.4.0" +eth-typing = ">=3.3.0" +eth-utils = ">=2.0.0" lru-dict = ">=1.1.6" -mypy-extensions = ">=1.0.0" -py-ecc = ">=1.4.7,<7.0.0" -rlp = ">=3,<4" -trie = ">=2.0.0,<3" +py-ecc = ">=1.4.7" +rlp = ">=3.0.0" +trie = ">=2.0.0" [package.extras] -benchmark = ["termcolor (>=1.1.0,<2.0.0)", "web3 (>=4.1.0,<5.0.0)"] -dev = ["Sphinx (>=1.5.5,<2)", "black (>=23)", "bumpversion (>=0.5.3,<1)", "cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (==2.11.1)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=5,<6)", "idna (==2.7)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "jinja2 (>=3.0.0,<3.1.0)", "lru-dict (>=1.1.6)", "mypy (==1.4.0)", "mypy-extensions (>=1.0.0)", "pexpect (>=4.6,<5)", "py-ecc (>=1.4.7,<7.0.0)", "py-evm (>=0.2.0-a.14)", "pydocstyle (>=6.0.0)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=2.0.0,<3)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (>=3.0)", "requests (>=2.20,<3)", "rlp (>=3,<4)", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0,<3)", "twine", "types-setuptools", "wheel"] -docs = ["Sphinx (>=1.5.5,<2)", "jinja2 (>=3.0.0,<3.1.0)", "py-evm (>=0.2.0-a.14)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-asyncio (>=0.2.0,<0.4)", "towncrier (>=21,<22)"] -eth = ["cached-property (>=1.5.1,<2)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0,<0.5.0)", "eth-typing (>=3.3.0,<4.0.0)", "eth-utils (>=2.0.0,<3.0.0)", "lru-dict (>=1.1.6)", "mypy-extensions (>=1.0.0)", "py-ecc (>=1.4.7,<7.0.0)", "rlp (>=3,<4)", "trie (>=2.0.0,<3)"] -eth-extra = ["blake2b-py (>=0.2.0,<0.3.0)", "coincurve (>=18.0.0)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "importlib-metadata (<5.0)", "isort (>=5.10.1)", "mypy (==1.4.0)", "pydocstyle (>=6.0.0)", "types-setuptools"] -test = ["factory-boy (==2.11.1)", "hypothesis (>=5,<6)", "importlib-metadata (<5.0)", "pexpect (>=4.6,<5)", "pytest (>=6.2.4,<7)", "pytest-asyncio (>=0.10.0,<0.11)", "pytest-cov (==2.5.1)", "pytest-timeout (>=2.0.0,<3)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist (>=3.0)"] +benchmark = ["termcolor (>=1.1.0)", "web3 (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "cached-property (>=1.5.1)", "ckzg (>=0.4.3)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0)", "eth-typing (>=3.3.0)", "eth-utils (>=2.0.0)", "factory-boy (>=3.0.0)", "hypothesis (>=6,<7)", "ipython", "lru-dict (>=1.1.6)", "pre-commit (>=3.4.0)", "py-ecc (>=1.4.7)", "py-evm (>=0.8.0b1)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.20.0)", "pytest-cov (>=4.0.0)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=3.0)", "rlp (>=3.0.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-asyncio (>=0.2.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "trie (>=2.0.0)", "twine", "wheel"] +docs = ["py-evm (>=0.8.0b1)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-asyncio (>=0.2.0)", "towncrier (>=21,<22)"] +eth = ["cached-property (>=1.5.1)", "ckzg (>=0.4.3)", "eth-bloom (>=1.0.3)", "eth-keys (>=0.4.0)", "eth-typing (>=3.3.0)", "eth-utils (>=2.0.0)", "lru-dict (>=1.1.6)", "py-ecc (>=1.4.7)", "rlp (>=3.0.0)", "trie (>=2.0.0)"] +eth-extra = ["blake2b-py (>=0.2.0)", "coincurve (>=18.0.0)"] +test = ["factory-boy (>=3.0.0)", "hypothesis (>=6,<7)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.20.0)", "pytest-cov (>=4.0.0)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=3.0)"] [[package]] name = "pycodestyle" @@ -2108,13 +2280,13 @@ files = [ [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -2158,6 +2330,116 @@ files = [ {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] +[[package]] +name = "pydantic" +version = "2.7.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"}, + {file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.3" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"}, + {file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"}, + {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"}, + {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"}, + {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"}, + {file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"}, + {file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"}, + {file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"}, + {file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"}, + {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"}, + {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"}, + {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"}, + {file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"}, + {file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"}, + {file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"}, + {file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"}, + {file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"}, + {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"}, + {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"}, + {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"}, + {file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"}, + {file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"}, + {file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"}, + {file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"}, + {file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"}, + {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"}, + {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"}, + {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"}, + {file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"}, + {file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"}, + {file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"}, + {file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"}, + {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"}, + {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"}, + {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"}, + {file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"}, + {file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"}, + {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"}, + {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"}, + {file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyflakes" version = "2.4.0" @@ -2171,33 +2453,29 @@ files = [ [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyproject-hooks" -version = "1.0.0" +version = "1.1.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" files = [ - {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, - {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, + {file = "pyproject_hooks-1.1.0-py3-none-any.whl", hash = "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2"}, + {file = "pyproject_hooks-1.1.0.tar.gz", hash = "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965"}, ] -[package.dependencies] -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "pyreadline" version = "2.1" @@ -2240,6 +2518,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pytest-forked" version = "1.6.0" @@ -2271,18 +2567,18 @@ pytest = "*" [[package]] name = "pytest-xdist" -version = "3.5.0" +version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, - {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] [package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" +execnet = ">=2.1" +pytest = ">=7.0.0" [package.extras] psutil = ["psutil (>=3.0)"] @@ -2361,101 +2657,104 @@ files = [ [[package]] name = "rapidfuzz" -version = "3.6.1" +version = "3.9.2" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.8" files = [ - {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f"}, - {file = "rapidfuzz-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-win32.whl", hash = "sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe"}, - {file = "rapidfuzz-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-win32.whl", hash = "sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1"}, - {file = "rapidfuzz-3.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-win32.whl", hash = "sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105"}, - {file = "rapidfuzz-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-win32.whl", hash = "sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198"}, - {file = "rapidfuzz-3.6.1-cp39-cp39-win_arm64.whl", hash = "sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e"}, - {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c"}, - {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6"}, - {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05"}, - {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c"}, - {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9"}, - {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87"}, - {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2"}, - {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b"}, - {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec"}, - {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b"}, - {file = "rapidfuzz-3.6.1.tar.gz", hash = "sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45e0c3e279e70589381f47ad410de7211bac943e827eb09eb8339d2124abca90"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:280ef2f3066df9c486ffd3874d2489978fb8021044c47c006eb96be8d47917d7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe128ac0e05ca3a71d8ff18e70884a64fde00b6fbd2b4d9f59f7a4d798257c55"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fbc0f6e1b6f4063b937d0edcf0a56cbc1d7179ade9b7d6c849c94e44a7b20f6"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df19455c2fb85e86a721111b84ac8dd3685194f0edc9faefb226731ad3e134a7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:801a5d97c465a3467b3cdf50cdcdadec129ddca582b24430f5d24c715c80be9b"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f218524596d261a6cb33cda965687e62dd30def478d39f0befa243642c3985"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5c61d53f293b4e3286919b0e081513367afabcb5aef0b6f899d006117778e558"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0ed70fc6627ae37319f822e5d8d21d561044e0b3331b6f0e6904476faa8d8ed7"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96fa229d06ee005d2f46374fb2af65590a590a6fa2fd56e66474829f5fa9adfe"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6609e881b57cabb40d515cc226bbf570e32e768bd2cc688ba026a45ffbc60875"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:204fd4d293ef4d409c4142ddf830b7613924b998670f67e512ab1f880a60218a"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win32.whl", hash = "sha256:5b331a09446bc8f8971cf488c9e6c0f7dbf2739828588e063cf08fd400638a24"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:01a9975984953fe549649e6a4c3f0d9c60707acf458184ec09678d6a57560112"}, + {file = "rapidfuzz-3.9.2-cp310-cp310-win_arm64.whl", hash = "sha256:ca4af5d7fc9c17bdc498aa1cab9ecf5140c8535c9cedeba1990bbe4b8be75098"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:300ab53981a5d6831fe7e0f30c407c79520ad0f0ab51b2cece8717689026f495"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4828642acdb075154ce2ff3260f8afb6a17b5b0c8a437efbadac06e9995dd7b"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b262883c3ce93dee1a9a974992961c8098e96b8142e2e01cabdb15ea8105c4a"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8582d85e35641734d6c1f43eb37c1f2a5eda338d3cfa8e651e078246b9ec58"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e33b61ef87e1876d216c479fa2256233b3bb0424465ab2db1d94ab7b8649ae1c"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fa1b3eb21756003a6a3977847dd4e0e9a26e2e02731d9daa5e92a9258e7f0db"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923ae0301a56356364f1159e3005fbeb2191e7a0e8705d5cc1b481d9eea27b97"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8e4041cfd87f0a022aa8a9a187d3b0824e35be2bd9b3bceada11578ddd9ad65"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1f832b430f976727bdbba009ee64acda25412602976fbfb2113d41e765d81849"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6ce5e57e0c6acf5a98ffbdfaf8bccb6e41fbddb9eda3e041f4cc69b7cade5fa0"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d65f34e71102d9cbe733d4ba1c645e7623eef850562501bab1ac79d217831436"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dd9ba4df0db46b9f909289e4687cc7721c622985c4cd169969005dd30fc1e24"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win32.whl", hash = "sha256:34c8bca3fef33d7e71f290de68be2184fac7a9e136fa0ed22b17ec597e181406"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e1a8872c0b8aef95c33db86d25e8bdea6f557b9cdf683123c25035b2bcfb8e"}, + {file = "rapidfuzz-3.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed02d73e46b7a4604d2bc1e0364b25f204862d40dd162f6b36ee22b9bf6d9df2"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ae6c4ba2778b097397968130f2b0cb795cdc415c115539a49ce798f606152ad5"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7270556ddebaa98fb777f493f17ed6a733b3527de16c43342bce1db109042845"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4625273447bdd94f2ab06b2951cd8b74356c3a48552208279a3ec2947ceee141"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5107b5ec8821453f7cac70b2d0bc4866699b25bff4819ada8b28bf2b11e87f65"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b04c851d309df8261ed42951444db657936234ceddf4032f4409b0214c95ecbe"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aeefff80f3f5d6841c30ffe0cdc84d62874de5a64cff509ae26fbd7478297af8"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cdc106b5a99edd46443449c767287dbb5d4464a7536475a365e368e7ee4d651"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce253a2b7a71a01a4abac71ac31fd05f6ac1f1cd2af2d98fa80fe5c402175e54"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c30407cadbfe99753b7a996f0dd6da490b1e27d318c01db227e8f49770a01ec"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3fc387783f70387a91aababd8a5faeb230931b655ad99bcf838cd72404ba66"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c409852a89535ec8720301a847bab198c1c14d0f34ed07dfabbb90b1dbfc506d"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8603050e547249c1cc8a8dc6a49917076572ea69b04bc51eb1748c403cfc9f46"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win32.whl", hash = "sha256:77bdb96e82d8831f0dd6db83e2ff0d4a731cff53e926d029c65a1dc3ae0f160a"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:09f354fa28e0fd170c6e4eea5e97eea0dba43761067df93109f49a5414ca8584"}, + {file = "rapidfuzz-3.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:168299c9a2b4f20f10c1bb96d8da0bb05bf1f3b9957be3a0bae5db65ce9f095f"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d87621d60078f87cb52082b1cbf9849afeaa1cb6d0a2b072fce25fe21c8675b4"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c447d0e534418ef3eaabcd890d85c7e9f289c1c6ef6e060a0b1f239799781747"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7161b205f25eff5f88ab809fb05a2a102634e06f452c0deb9535c9f41cd7b0a"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f13a6bbadba8fdd42676c1213ebc692bba9fac00f7db0ae92acc06bb734294c4"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54534743820a15bd0dc30a0a0010825be337973236550fd63587700a7950bbca"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea61851a4c2f93148aa2779458fb3f70a62342d77c9ec3d9d08445c8485b738"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e941f81a60351a842976fea208e6a6701a5899eb8a80b907e57d7c3099337900"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1bbfaf439e48efe3a48cada946cf7678b09c818ce9668e09dac40d05b772f6f8"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:574f464da18d660712e9776072572d462cf6a26144c833d18d9c93778286e023"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8a56c494246d29aacf5ac93ca3cf338d79588a1a5c05d8f496c3f4d7127e9031"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2943b0f17195c000948a7668bb11979ea0e50079a3d3db9d139e51b68c3a7c26"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:27214f93555d4f9b7b1baf107a6ba13e9daee21f1ec6e36418556d04a7ee4d9b"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-win32.whl", hash = "sha256:876c6628fec6241262c27f8fda3c73bab88e205e9b9394c8868361e2eda59048"}, + {file = "rapidfuzz-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf1952b486589ffcfbde2015ca9be15e0f4b0e63d1e2c25f3daec0263fda4e69"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ca9a135060ee4d887d6af86493c3e0eb1c99ca205bca943fe5994dc93e648d5"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:723518c9a18e8bda996d77aa9307b6f8b0e77905702b2772b020adf24191073a"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65eb9aeae73ac60e53a9d6c509daaa217ea256a5e184eb8920c9b15295c48677"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef2964f4eb9a37487c96e5e32167a3c4fa51bf8e899853d0ac67e0465a27702c"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c64a252c96f29667c206726903bb9705c5195f01850360c9b9268de92ac878dc"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b32b03398517b5e33c7f36d625a00fcb1c955b9fe3c939325688175fb21730"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec5f7b1bac77439b624f5acbd8bfe61e7b833678701068b43f7a489c151427c0"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5fd1b49fba8b4b9172eed5b131c1e9864d4d76bebea34359274f16a3591e5f44"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c05b033fc3ff043f48e744f67038af7fd34003047c7810f24bec7c01ce7da05b"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3bea20db89b510d78d017b349b9d87159c32418693ddf091d9035dbe20b4dc0"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:77226a77590f83ee073f4f8cc86a1232da88e24d19d349361faa169fb17ba1cd"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:83ed8bc2c942dc61ab739bbca1ead791143b4639dc92156d3060bd0b6f4541ea"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win32.whl", hash = "sha256:2db70f64974c10b76ae37d5cff6124dce791def815d4fdf5ac16fe60be88d905"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:bdead23114206dea4a22ed3aad6565b99a9e4b3fff9837c423afc556d2814b1a"}, + {file = "rapidfuzz-3.9.2-cp39-cp39-win_arm64.whl", hash = "sha256:0ec69ad076cfc7c88323d671613e40bb8754ba95a203556d9a7759e60f0544e8"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:018360654881e75131b227aa96cdaba543c438da881c70a12ca0c86e2c4083b2"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:eaa8178ec9238f32f15b6e49f70b852accda0a848448c4e30bce77c6624ebaba"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dd79b0f90ce609df96d0d48ef4327cf1f0415b9274588a466d3610a775d2f9"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04a1c38a72a50f3e6d346a33d53fa51ba390552b3592fca64a07e54d749b439b"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ca96eec40e815f0cf10b00008f295fd26ca43792a844cf62588a8ea614e160"}, + {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c01c515a928f295f49d588b6523f44b474f047f9f2de0079bc57bcd00b870778"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07e14ef260b6f4ee03dff07a0ac95a16aff1ddbc7e6171e07e49d2d61526f3be"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:64f3480bddc12b89969930f12a50a1aeb53e09aad41cf8b27694d83ca1cc7864"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3c9e33ec21755bda1878095537cb84848e9cf6510d4837d22144ba04e33df29"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a70045e84225697ddf67d656aa25b70d6802e2ff339d51f9545fca5b9b13fb8c"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ec1fd328518c33adb9171afe8735137cb7b492e4a81cddc23568f9980c235c"}, + {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1fd8458fdac232766d55593c1228c70968f382fdc376c25685273f99b5d1d921"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a373748fddb5403b562b6d682082de360bb08395f44e3cb7e74819461e39a16c"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:45f80856db3e22cb5f96ad1572aa1d004714514625ed4668144661d8a7c7e61f"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e52cf878e0ccbbad0744eb3e2bb83a784645b146f15611bac225bc218f19b"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbe4d3034a8cfe59a2b477375ad7d739b3e5935f10af08abdf64aae55780cad"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd38abfda97e42b30093f207108dcba944beab1edf6624ba757cf57354063177"}, + {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:16b41fe360387283a3184ce72d4d26d1928e7ce809268a88e8491a776dd770af"}, + {file = "rapidfuzz-3.9.2.tar.gz", hash = "sha256:c899d78709f8d4bd0059784fa27a9f6c53d04fc4aeaa21de7c0c8e34a7154e88"}, ] [package.extras] @@ -2463,13 +2762,13 @@ full = ["numpy"] [[package]] name = "referencing" -version = "0.33.0" +version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, - {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, ] [package.dependencies] @@ -2478,115 +2777,101 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2023.12.25" +version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -2615,13 +2900,13 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [package.dependencies] @@ -2633,131 +2918,130 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rlp" -version = "3.0.0" -description = "A package for Recursive Length Prefix encoding and decoding" +version = "4.0.1" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" optional = false -python-versions = "*" +python-versions = "<4,>=3.8" files = [ - {file = "rlp-3.0.0-py2.py3-none-any.whl", hash = "sha256:d2a963225b3f26795c5b52310e0871df9824af56823d739511583ef459895a7d"}, - {file = "rlp-3.0.0.tar.gz", hash = "sha256:63b0465d2948cd9f01de449d7adfb92d207c1aef3982f20310f8009be4a507e8"}, + {file = "rlp-4.0.1-py3-none-any.whl", hash = "sha256:ff6846c3c27b97ee0492373aa074a7c3046aadd973320f4fffa7ac45564b0258"}, + {file = "rlp-4.0.1.tar.gz", hash = "sha256:bcefb11013dfadf8902642337923bd0c786dc8a27cb4c21da6e154e52869ecb1"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" +eth-utils = ">=2" [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] -lint = ["flake8 (==3.4.1)"] -rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] -test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +rust-backend = ["rusty-rlp (>=0.2.1)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "rpds-py" -version = "0.17.1" +version = "0.18.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, - {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, - {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, - {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, - {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, - {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, - {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, - {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, - {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, - {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, - {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, - {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, - {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, - {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, - {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, - {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, - {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, - {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, - {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, - {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, - {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, - {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, - {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, - {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, - {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, - {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, - {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, - {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, - {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, - {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, - {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, - {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, - {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, - {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, - {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, - {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, - {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, - {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, - {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, - {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, - {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, - {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, - {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, - {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, + {file = "rpds_py-0.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53"}, + {file = "rpds_py-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1"}, + {file = "rpds_py-0.18.1-cp310-none-win32.whl", hash = "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333"}, + {file = "rpds_py-0.18.1-cp310-none-win_amd64.whl", hash = "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a"}, + {file = "rpds_py-0.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8"}, + {file = "rpds_py-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88"}, + {file = "rpds_py-0.18.1-cp311-none-win32.whl", hash = "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb"}, + {file = "rpds_py-0.18.1-cp311-none-win_amd64.whl", hash = "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2"}, + {file = "rpds_py-0.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3"}, + {file = "rpds_py-0.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a"}, + {file = "rpds_py-0.18.1-cp312-none-win32.whl", hash = "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6"}, + {file = "rpds_py-0.18.1-cp312-none-win_amd64.whl", hash = "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72"}, + {file = "rpds_py-0.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74"}, + {file = "rpds_py-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc"}, + {file = "rpds_py-0.18.1-cp38-none-win32.whl", hash = "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9"}, + {file = "rpds_py-0.18.1-cp38-none-win_amd64.whl", hash = "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2"}, + {file = "rpds_py-0.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93"}, + {file = "rpds_py-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26"}, + {file = "rpds_py-0.18.1-cp39-none-win32.whl", hash = "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360"}, + {file = "rpds_py-0.18.1-cp39-none-win_amd64.whl", hash = "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e"}, + {file = "rpds_py-0.18.1.tar.gz", hash = "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f"}, ] [[package]] @@ -2775,22 +3059,6 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" -[[package]] -name = "setuptools" -version = "69.0.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "shellingham" version = "1.5.4" @@ -2845,7 +3113,7 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "titanoboa" -version = "0.1.8" +version = "0.1.10b1" description = "A Vyper interpreter" optional = false python-versions = "*" @@ -2854,14 +3122,16 @@ develop = false [package.dependencies] eth-abi = "*" -eth-account = "*" +eth-account = ">=0.13.0" eth-stdlib = ">=0.2.7,<0.3.0" eth-typing = "*" hypothesis = "*" -py-evm = ">=0.7.0a4" +py-evm = ">=0.10.0b4" pytest = "*" +pytest-cov = "*" requests = "*" rich = "*" +typing-extensions = "*" vyper = ">=0.3.10" [package.extras] @@ -2869,9 +3139,9 @@ forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/vyperlang/titanoboa.git" -reference = "8343f16bd35829421c0953373aa854fc52b41170" -resolved_reference = "8343f16bd35829421c0953373aa854fc52b41170" +url = "https://github.com/DanielSchiavini/titanoboa.git" +reference = "zksync" +resolved_reference = "f0a25396a8256b6cbb7d245a840298e30d705c87" [[package]] name = "tomli" @@ -2886,13 +3156,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.3" +version = "0.12.5" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, - {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, + {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, + {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] [[package]] @@ -2908,34 +3178,34 @@ files = [ [[package]] name = "traitlets" -version = "5.14.1" +version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, - {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "trie" -version = "2.2.0" +version = "3.0.1" description = "Python implementation of the Ethereum Trie structure" optional = false -python-versions = ">=3.7, <4" +python-versions = "<4,>=3.8" files = [ - {file = "trie-2.2.0-py3-none-any.whl", hash = "sha256:b6ad00305722b271cd05c9475e741c92a61f0ca53e6cc4fa9a5591e37eac34ca"}, - {file = "trie-2.2.0.tar.gz", hash = "sha256:117a6f0844eb60f2f68ed45e621886690dacd16343394c1adfb3ff44231725bc"}, + {file = "trie-3.0.1-py3-none-any.whl", hash = "sha256:fbe90011a28f4fc6597bc83706589c2a74c81c8b1410c5e16eebfae0e9796464"}, + {file = "trie-3.0.1.tar.gz", hash = "sha256:3f53adaa04726eb23cb786b0118e62d1f2fb6ed0a7968be964dc34aba27380ee"}, ] [package.dependencies] eth-hash = ">=0.1.0" eth-utils = ">=2.0.0" -hexbytes = ">=0.2.0,<0.4.0" +hexbytes = ">=0.2.3" rlp = ">=3" sortedcontainers = ">=2.1.0" @@ -2946,24 +3216,24 @@ test = ["hypothesis (>=6.56.4,<7)", "pycryptodome", "pytest (>=7.0.0)", "pytest- [[package]] name = "trove-classifiers" -version = "2024.1.8" +version = "2024.5.22" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" files = [ - {file = "trove-classifiers-2024.1.8.tar.gz", hash = "sha256:6e36caf430ff6485c4b57a4c6b364a13f6a898d16b9417c6c37467e59c14b05a"}, - {file = "trove_classifiers-2024.1.8-py3-none-any.whl", hash = "sha256:3c1ff4deb10149c7e39ede6e5bbc107def64362ef1ee7590ec98d71fb92f1b6a"}, + {file = "trove_classifiers-2024.5.22-py3-none-any.whl", hash = "sha256:c43ade18704823e4afa3d9db7083294bc4708a5e02afbcefacd0e9d03a7a24ef"}, + {file = "trove_classifiers-2024.5.22.tar.gz", hash = "sha256:8a6242bbb5c9ae88d34cf665e816b287d2212973c8777dfaef5ec18d72ac1d03"}, ] [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -2984,13 +3254,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -2999,7 +3269,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -3051,13 +3321,13 @@ files = [ [[package]] name = "wheel" -version = "0.42.0" +version = "0.43.0" description = "A built-package format for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, - {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, + {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, + {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, ] [package.extras] @@ -3166,20 +3436,20 @@ cffi = ">=1.0" [[package]] name = "zipp" -version = "3.17.0" +version = "3.19.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.19.0-py3-none-any.whl", hash = "sha256:96dc6ad62f1441bcaccef23b274ec471518daf4fbbc580341204936a5a3dddec"}, + {file = "zipp-3.19.0.tar.gz", hash = "sha256:952df858fb3164426c976d9338d3961e8e8b3758e2e059e0f754b8c4262625ee"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d46c8db7b93a0d5ab622f7d07f347e7ff492bf0a9f209355ddffaf8fb2e50f6c" +content-hash = "0ea00c3e42641a769590bdee9c4cb74c6e895703c14c3cab81129fda4cce571f" diff --git a/pyproject.toml b/pyproject.toml index 6214d430..11092ace 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "8343f16bd35829421c0953373aa854fc52b41170"} +titanoboa = {git = "https://github.com/DanielSchiavini/titanoboa.git", rev = "zksync"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" @@ -23,8 +23,8 @@ mamushi = "^0.0.2a1" [tool.poetry.group.testing.dependencies] -eip712 = "^0.2.1" -eth-account = "~0.8.0" +eip712 = {git = "https://github.com/DanielSchiavini/eip712.git", rev = "main"} +eth-account = "~0.13.0" ipython = "^8.14.0" hypothesis = "^6.79.3" pytest = "^7.4.0" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index ce63d9e6..ccc2691a 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -2,12 +2,15 @@ import sys import boa +import boa_zksync import deployment_utils as deploy_utils from boa.network import NetworkEnv from eth_abi import encode from eth_account import Account from rich.console import Console as RichConsole +ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" + logger = RichConsole(file=sys.stdout) deployments = { @@ -185,10 +188,18 @@ "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", "zap": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", }, + "zksync:mainnet": { + "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", + "views": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", + "plain_amm": "", + "meta_amm": "", + "factory": "", + "zap": "", + }, } -def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: +def set_contract_pragma(contract_file, network) -> boa.contracts.vyper.vyper_contract.VyperDeployer: with open(contract_file, "r") as f: source = f.read() @@ -203,12 +214,24 @@ def set_evm_version(contract_file, network) -> boa.vyper.contract.VyperDeployer: else: # all looks good ... new_source = source - contract_obj = boa.loads_partial(source_code=new_source) + is_zksync = "zksync" in network + if is_zksync: + logger.log("Cannot use compiler optimisations in zksync. Removing optimizer flags") + if "# pragma optimize gas" in new_source: + new_source = source.replace("# pragma optimize gas\n", "#\n") + if "# pragma optimize codesize" in new_source: + new_source = source.replace("# pragma optimize codesize\n", "#\n") + + contract_obj = boa.loads_partial(source_code=new_source, filename=contract_file) return contract_obj def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): deployed_contract = deployments[network][contract_designation] + try: + contract_name = os.path.basename(contract_obj.filename) + except AttributeError: + contract_name = os.path.basename(contract_obj._filename) if not deployed_contract: logger.log(f"Deploying {contract_designation} contract ...") @@ -218,7 +241,49 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo constructor_args = encode(["address", "address"], args) logger.log(f"Constructor arguments for {contract_designation}: {constructor_args.hex()}") else: - contract = contract_obj.deploy_as_blueprint() + if "zksync:mainnet" in network: + if "CurveStableSwapNG.vy" == contract_name: + contract = contract_obj.deploy_as_blueprint( + "blueprint", # name + "BLUEPRINT", # symbol + 1500, # A + 1000000, # fee + 10000000000, # offpeg_fee_multiplier + 865, # ma_exp_time + [ + "0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4", # native usdc + "0x493257fD37EDB34451f62EDf8D2a0C418852bA4C", # native usdt + ], + [10**18, 10**18], # rate_multipliers + [0, 0], # asset_types + [b"", b""], # method_ids + [ZERO_ADDRESS, ZERO_ADDRESS], # oracles + ) + + elif "CurveStableSwapMetaNG.vy" == contract_name: + contract = contract_obj.deploy_as_blueprint( + "blueprint", # name + "BLUEPRINT", # symbol + 500, # A + 1000000, # fee + 50000000000, # offpeg_fee_multiplier + 866, # ma_exp_time + ZERO_ADDRESS, # math_implementation + ZERO_ADDRESS, # base_pool + ["0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"], # coins (bridged usdc) + [ + "0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4", # native usdc + "0x493257fD37EDB34451f62EDf8D2a0C418852bA4C", # native usdt + ], # base coins + [10**18], # rate_multipliers + [0], # asset_types + [b""], # method_ids + [ZERO_ADDRESS], # oracles + ) + + else: + contract = contract_obj.deploy_as_blueprint() + logger.log(f"Deployed! At: {contract.address}.") else: logger.log(f"Deployed {contract_designation} contract exists. Using {deployed_contract} ...") @@ -230,14 +295,26 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo def deploy_infra(network, url, account, fork=False): logger.log(f"Deploying on {network} ...") - if fork: - boa.env.fork(url) - logger.log("Forkmode ...") - boa.env.eoa = deploy_utils.FIDDYDEPLOYER # set eoa address here + if network == "zksync:mainnet": + if not fork: + boa_zksync.set_zksync_env(url) + logger.log("Prodmode on zksync Era ...") + boa.env.add_account(Account.from_key(os.environ[account])) + else: + boa_zksync.set_zksync_fork(url) + logger.log("Forkmode on zksync Era ...") + boa.env.eoa = deploy_utils.FIDDYDEPLOYER # set eoa address here + else: - logger.log("Prodmode ...") - boa.set_env(NetworkEnv(url)) - boa.env.add_account(Account.from_key(os.environ[account])) + if fork: + boa.env.fork(url) + logger.log("Forkmode ...") + boa.env.eoa = deploy_utils.FIDDYDEPLOYER # set eoa address here + else: + logger.log("Prodmode ...") + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + for _network, data in deploy_utils.curve_dao_network_settings.items(): if _network in network: owner = data.dao_ownership_contract @@ -249,10 +326,10 @@ def deploy_infra(network, url, account, fork=False): # --------------------- Deploy math, views, blueprints --------------------- # get source and set evm_version - math_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGMath.vy", network) - views_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNGViews.vy", network) - plain_contract_obj = set_evm_version("./contracts/main/CurveStableSwapNG.vy", network) - meta_contract_obj = set_evm_version("./contracts/main/CurveStableSwapMetaNG.vy", network) + math_contract_obj = set_contract_pragma("./contracts/main/CurveStableSwapNGMath.vy", network) + views_contract_obj = set_contract_pragma("./contracts/main/CurveStableSwapNGViews.vy", network) + plain_contract_obj = set_contract_pragma("./contracts/main/CurveStableSwapNG.vy", network) + meta_contract_obj = set_contract_pragma("./contracts/main/CurveStableSwapMetaNG.vy", network) # deploy non-blueprint contracts: math_contract = check_and_deploy(math_contract_obj, "math", network) @@ -263,12 +340,12 @@ def deploy_infra(network, url, account, fork=False): meta_blueprint = check_and_deploy(meta_contract_obj, "meta_amm", network, blueprint=True) # Factory: - factory_contract_obj = set_evm_version("./contracts/main/CurveStableSwapFactoryNG.vy", network) + factory_contract_obj = set_contract_pragma("./contracts/main/CurveStableSwapFactoryNG.vy", network) args = [fee_receiver, deploy_utils.FIDDYDEPLOYER] factory = check_and_deploy(factory_contract_obj, "factory", network, False, args) # zap: - zap_contract_obj = set_evm_version("./contracts/main/MetaZapNG.vy", network) + zap_contract_obj = set_contract_pragma("./contracts/main/MetaZapNG.vy", network) check_and_deploy(zap_contract_obj, "zap", network) # Set up AMM implementations:÷ @@ -298,7 +375,7 @@ def deploy_infra(network, url, account, fork=False): if "ethereum" in network: # Gauge contract only for Ethereum. logger.log("Deploying and setting up Gauge contracts ...") - gauge_contract_obj = set_evm_version("./contracts/main/LiquidityGauge.vy", network) + gauge_contract_obj = set_contract_pragma("./contracts/main/LiquidityGauge.vy", network) gauge_blueprint = check_and_deploy(gauge_contract_obj, "gauge", network, blueprint=True) if not factory.gauge_implementation() == gauge_blueprint.address: @@ -310,9 +387,9 @@ def deploy_infra(network, url, account, fork=False): def main(): deployer = "FIDDYDEPLOYER" - fork = False - network = "xlayer" - rpc = "https://xlayerrpc.okx.com" + fork = True + network = "zksync" + rpc = "https://mainnet.era.zksync.io" deploy_infra(f"{network}:mainnet", rpc, deployer, fork=fork) diff --git a/scripts/deploy_proxy_admin.py b/scripts/deploy_proxy_admin.py index 6fb14c58..9e403973 100644 --- a/scripts/deploy_proxy_admin.py +++ b/scripts/deploy_proxy_admin.py @@ -2,8 +2,9 @@ import sys import boa +import boa_zksync from boa.network import NetworkEnv -from deploy_infra import set_evm_version +from deploy_infra import set_contract_pragma from deployment_utils import BABE, FIDDYDEPLOYER from eth_abi import encode from eth_account import Account @@ -15,17 +16,24 @@ def deploy_proxy_admin(network, url, account, fork=False): logger.log(f"Deploying ProxyAdmin for {network} ...") - if fork: - boa.env.fork(url) - logger.log("Forkmode ...") - boa.env.eoa = FIDDYDEPLOYER - else: - logger.log("Prodmode ...") - boa.set_env(NetworkEnv(url)) + if network == "zksync": + assert not fork + boa_zksync.set_zksync_env(url) + logger.log("Prodmode on zksync Era ...") boa.env.add_account(Account.from_key(os.environ[account])) + else: + if fork: + boa.env.fork(url) + logger.log("Forkmode ...") + boa.env.eoa = FIDDYDEPLOYER + else: + logger.log("Prodmode ...") + boa.set_env(NetworkEnv(url)) + boa.env.add_account(Account.from_key(os.environ[account])) + # deploy thin proxy if no owners exist: - proxy_admin_contract_obj = set_evm_version("./contracts/ProxyAdmin.vy", network) + proxy_admin_contract_obj = set_contract_pragma("./contracts/ProxyAdmin.vy", network) args = [FIDDYDEPLOYER, BABE] encoded_args = encode(["address", "address"], args).hex() logger.log(f"Constructor: {encoded_args}") @@ -34,8 +42,8 @@ def deploy_proxy_admin(network, url, account, fork=False): def main(): - network = "xlayer" - rpc = "https://xlayerrpc.okx.com" + network = "zksync" + rpc = "https://mainnet.era.zksync.io" account = "FIDDYDEPLOYER" fork = False deploy_proxy_admin(network, rpc, account, fork) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index ece9aee5..e3463339 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -58,7 +58,8 @@ class CurveNetworkSettings: fee_receiver_address="0xf3A431008396df8A8b2DF492C913706BDB0874ef", ), "zksync:mainnet": CurveNetworkSettings( - dao_ownership_contract="", fee_receiver_address="0x4920088D9a5e5De9c098FCA4960d0DA5f4caa4c1" + dao_ownership_contract="0xCb8799BFF48bb549F7B69Bb9BE60DbA7cd4F1BB7", + fee_receiver_address="0xCb8799BFF48bb549F7B69Bb9BE60DbA7cd4F1BB7", ), "pzkevm:mainnet": CurveNetworkSettings( dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", From e47c29de56c26c13d7d499379f9c158ed6213f9a Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:01:11 +0200 Subject: [PATCH 329/337] issues fetching contract state for zksync deployment --- scripts/deploy_infra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index ccc2691a..afaa764e 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -269,7 +269,7 @@ def check_and_deploy(contract_obj, contract_designation, network, blueprint: boo 50000000000, # offpeg_fee_multiplier 866, # ma_exp_time ZERO_ADDRESS, # math_implementation - ZERO_ADDRESS, # base_pool + "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4", # base_pool (but we use bridged usdc) ["0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4"], # coins (bridged usdc) [ "0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4", # native usdc @@ -299,11 +299,11 @@ def deploy_infra(network, url, account, fork=False): if not fork: boa_zksync.set_zksync_env(url) logger.log("Prodmode on zksync Era ...") - boa.env.add_account(Account.from_key(os.environ[account])) else: boa_zksync.set_zksync_fork(url) logger.log("Forkmode on zksync Era ...") - boa.env.eoa = deploy_utils.FIDDYDEPLOYER # set eoa address here + + boa.env.set_eoa(Account.from_key(os.environ[account])) else: if fork: From 48658dbd5d665620d56ec89919c8d35ffd75f8f2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:17:41 +0200 Subject: [PATCH 330/337] deployed factory; amm implementations awaiting ... --- scripts/deploy_infra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index afaa764e..0be5dae4 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -193,8 +193,8 @@ "views": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", "plain_amm": "", "meta_amm": "", - "factory": "", - "zap": "", + "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", + "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", }, } @@ -387,7 +387,7 @@ def deploy_infra(network, url, account, fork=False): def main(): deployer = "FIDDYDEPLOYER" - fork = True + fork = False network = "zksync" rpc = "https://mainnet.era.zksync.io" deploy_infra(f"{network}:mainnet", rpc, deployer, fork=fork) From 4f4475a698781919bf830321c44e9cfa90a81d4e Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:14:18 +0200 Subject: [PATCH 331/337] deployed on zksync --- poetry.lock | 27 ++++++++++++++++++++++++--- pyproject.toml | 2 +- scripts/deploy_infra.py | 6 +++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 315a6f9b..69fcafa9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3139,9 +3139,30 @@ forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/DanielSchiavini/titanoboa.git" +url = "https://github.com/DanielSchiavini/titanoboa" reference = "zksync" -resolved_reference = "f0a25396a8256b6cbb7d245a840298e30d705c87" +resolved_reference = "f96fff3450c060b6849a632608377b4477667884" + +[[package]] +name = "titanoboa-zksync" +version = "0.0.1" +description = "A Zksync plugin for the Titanoboa Vyper interpreter" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +titanoboa = {git = "https://github.com/DanielSchiavini/titanoboa", rev = "zksync"} + +[package.extras] +forking-recommended = ["ujson"] + +[package.source] +type = "git" +url = "https://github.com/DanielSchiavini/titanoboa-zksync.git" +reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" +resolved_reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" [[package]] name = "tomli" @@ -3452,4 +3473,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0ea00c3e42641a769590bdee9c4cb74c6e895703c14c3cab81129fda4cce571f" +content-hash = "251db9973fb842214b8fa1401ec565f64f6e82e3aac76755a4a26fab226fca05" diff --git a/pyproject.toml b/pyproject.toml index 11092ace..0d8ca2c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,10 @@ packages = [] [tool.poetry.dependencies] python = "^3.10" poetry = "1.5.1" -titanoboa = {git = "https://github.com/DanielSchiavini/titanoboa.git", rev = "zksync"} vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" +titanoboa-zksync = {git = "https://github.com/DanielSchiavini/titanoboa-zksync.git", rev = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49"} [tool.poetry.group.dev.dependencies] black = "22.3.0" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 0be5dae4..9087bd04 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -191,8 +191,8 @@ "zksync:mainnet": { "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", "views": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", - "plain_amm": "", - "meta_amm": "", + "plain_amm": "0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4", + "meta_amm": "0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3", "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", }, @@ -363,7 +363,7 @@ def deploy_infra(network, url, account, fork=False): current_pool_impl = factory.pool_implementations(0) if not current_pool_impl == plain_blueprint.address: - logger.log(f"Curent 'plain' pool impl at index 0: {current_pool_impl}") + logger.log(f"Current 'plain' pool impl at index 0: {current_pool_impl}") factory.set_pool_implementations(0, plain_blueprint.address) logger.log(f"Set plain amm implementation at index 0 to: {plain_blueprint.address}") From b0269d36c4cc9b1f07dc7754cf2f9345556543ad Mon Sep 17 00:00:00 2001 From: curvefi Date: Fri, 7 Jun 2024 15:15:56 +0000 Subject: [PATCH 332/337] chore: add release file - zksync --- deployments/release-zksync-07-06-2024.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 deployments/release-zksync-07-06-2024.txt diff --git a/deployments/release-zksync-07-06-2024.txt b/deployments/release-zksync-07-06-2024.txt new file mode 100644 index 00000000..73502db7 --- /dev/null +++ b/deployments/release-zksync-07-06-2024.txt @@ -0,0 +1 @@ +math: 0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C views: 0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4 plain_amm: 0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4 meta_amm: 0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3 factory: 0x375444aeDEb6C3db897f293E1DBa85D7422A6859 zap: 0x4232Dcc6D31543A2431079BdE2082C69eA3A771E From a144b1a0b7617264b187530426f47946bd1e652d Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:02:41 +0200 Subject: [PATCH 333/337] init --- contracts/main/CurveStableSwapNGViews.vy | 218 ++++++++++++----------- 1 file changed, 115 insertions(+), 103 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 6bab1b8a..2f352b5c 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -20,22 +20,17 @@ interface StableSwapNG: def A() -> uint256: view def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def totalSupply() -> uint256: view - def calc_token_amount(amounts: DynArray[uint256, MAX_COINS], deposit: bool) -> uint256: view def offpeg_fee_multiplier() -> uint256: view -interface StableSwap2: - def calc_token_amount(amounts: uint256[2], deposit: bool) -> uint256: view - -interface StableSwap3: - def calc_token_amount(amounts: uint256[3], deposit: bool) -> uint256: view - - A_PRECISION: constant(uint256) = 100 MAX_COINS: constant(uint256) = 8 PRECISION: constant(uint256) = 10 ** 18 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 +VERSION: public(constant(String[8])) = "1.1.0" + + # ------------------------------ Public Getters ------------------------------ @@ -99,6 +94,7 @@ def get_dx_underlying( BASE_N_COINS: uint256 = StableSwapNG(pool).BASE_N_COINS() N_COINS: uint256 = StableSwapNG(pool).N_COINS() base_pool_has_static_fee: bool = self._has_static_fee(BASE_POOL) + base_pool_lp_token: address = StableSwapNG(pool).coins(1) # CASE 1: Swap does not involve Metapool at all. In this case, we kindly ask the user # to use the right pool for their swaps. @@ -114,7 +110,7 @@ def get_dx_underlying( if i == 0: # Calculate LP tokens that are burnt to receive dy amount of base_j tokens. lp_amount_burnt: uint256 = self._base_calc_token_amount( - dy, j - 1, BASE_N_COINS, BASE_POOL, False + dy, j - 1, BASE_N_COINS, BASE_POOL, base_pool_lp_token, False, ) return self._get_dx(0, 1, lp_amount_burnt, pool, False, N_COINS) @@ -225,83 +221,19 @@ def calc_token_amount( ) -> uint256: """ @notice Calculate addition or reduction in token supply from a deposit or withdrawal + @dev Only works for StableswapNG pools and not legacy versions @param _amounts Amount of each coin being deposited @param _is_deposit set True for deposits, False for withdrawals @return Expected amount of LP tokens received """ - amp: uint256 = StableSwapNG(pool).A() * A_PRECISION - N_COINS: uint256 = StableSwapNG(pool).N_COINS() - - rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - old_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) - - # Initial invariant - D0: uint256 = self.get_D(xp, amp, N_COINS) - - total_supply: uint256 = StableSwapNG(pool).totalSupply() - new_balances: DynArray[uint256, MAX_COINS] = old_balances - for i in range(MAX_COINS): - if i == N_COINS: - break - - amount: uint256 = _amounts[i] - if _is_deposit: - new_balances[i] += amount - else: - new_balances[i] -= amount - - # Invariant after change - for idx in range(MAX_COINS): - if idx == N_COINS: - break - xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D1: uint256 = self.get_D(xp, amp, N_COINS) - - # We need to recalculate the invariant accounting for fees - # to calculate fair user's share - D2: uint256 = D1 - if total_supply > 0: - - # Only account for fees if we are not the first to deposit - base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) - fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() - _dynamic_fee_i: uint256 = 0 - xs: uint256 = 0 - ys: uint256 = (D0 + D1) / N_COINS - - for i in range(MAX_COINS): - if i == N_COINS: - break - - ideal_balance: uint256 = D1 * old_balances[i] / D0 - difference: uint256 = 0 - new_balance: uint256 = new_balances[i] - if ideal_balance > new_balance: - difference = ideal_balance - new_balance - else: - difference = new_balance - ideal_balance - - xs = rates[i] * (old_balances[i] + new_balance) / PRECISION - _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) - new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR - - for idx in range(MAX_COINS): - if idx == N_COINS: - break - xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D2 = self.get_D(xp, amp, N_COINS) - else: - return D1 # Take the dust if there was any - - diff: uint256 = 0 - if _is_deposit: - diff = D2 - D0 - else: - diff = D0 - D2 - return diff * total_supply / D0 + return self._calc_token_amount( + _amounts, + _is_deposit, + pool, + pool, + StableSwapNG(pool).N_COINS() + ) @view @@ -449,36 +381,116 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin @internal @view -def _base_calc_token_amount( - dx: uint256, - base_i: int128, - base_n_coins: uint256, - base_pool: address, - is_deposit: bool +def _calc_token_amount( + _amounts: uint256[MAX_COINS], + is_deposit: bool, + pool: address, + pool_lp_token: address, + n_coins: uint256, ) -> uint256: - base_pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) - if base_n_coins == 2 and not base_pool_is_ng: + amp: uint256 = StableSwapNG(pool).A() * A_PRECISION + N_COINS: uint256 = StableSwapNG(pool).N_COINS() - base_inputs: uint256[2] = empty(uint256[2]) - base_inputs[base_i] = dx - return StableSwap2(base_pool).calc_token_amount(base_inputs, is_deposit) + rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + old_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) + xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - elif base_n_coins == 3 and not base_pool_is_ng: + pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) + if pool_is_ng: + rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) + else: + ... # TODO: accommodate if pool is not ng (but: cannot fully accommodate in a simple manner) - base_inputs: uint256[3] = empty(uint256[3]) - base_inputs[base_i] = dx - return StableSwap3(base_pool).calc_token_amount(base_inputs, is_deposit) + # Initial invariant + D0: uint256 = self.get_D(xp, amp, N_COINS) - else: + total_supply: uint256 = StableSwapNG(pool).totalSupply() + new_balances: DynArray[uint256, MAX_COINS] = old_balances + for i in range(MAX_COINS): + if i == N_COINS: + break + + amount: uint256 = _amounts[i] + if _is_deposit: + new_balances[i] += amount + else: + new_balances[i] -= amount + + # Invariant after change + for idx in range(MAX_COINS): + if idx == N_COINS: + break + xp[idx] = rates[idx] * new_balances[idx] / PRECISION + D1: uint256 = self.get_D(xp, amp, N_COINS) + + # We need to recalculate the invariant accounting for fees + # to calculate fair user's share + D2: uint256 = D1 + if total_supply > 0: + + # Only account for fees if we are not the first to deposit + base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) + fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() + _dynamic_fee_i: uint256 = 0 + xs: uint256 = 0 + ys: uint256 = (D0 + D1) / N_COINS + + for i in range(MAX_COINS): + if i == N_COINS: + break - base_inputs: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - for i in range(base_n_coins, bound=MAX_COINS): - if i == convert(base_i, uint256): - base_inputs.append(dx) + ideal_balance: uint256 = D1 * old_balances[i] / D0 + difference: uint256 = 0 + new_balance: uint256 = new_balances[i] + if ideal_balance > new_balance: + difference = ideal_balance - new_balance else: - base_inputs.append(0) - return StableSwapNG(base_pool).calc_token_amount(base_inputs, is_deposit) + difference = new_balance - ideal_balance + + xs = rates[i] * (old_balances[i] + new_balance) / PRECISION + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) + new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR + + for idx in range(MAX_COINS): + if idx == N_COINS: + break + xp[idx] = rates[idx] * new_balances[idx] / PRECISION + + D2 = self.get_D(xp, amp, N_COINS) + else: + return D1 # Take the dust if there was any + + diff: uint256 = 0 + if _is_deposit: + diff = D2 - D0 + else: + diff = D0 - D2 + return diff * total_supply / D0 + + +@internal +@view +def _base_calc_token_amount( + dx: uint256, + base_i: int128, + base_n_coins: uint256, + base_pool: address, + base_pool_lp_token: address, + is_deposit: bool, +) -> uint256: + + base_pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) + base_inputs: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + base_inputs[base_i] = dx + + return self._calc_token_amount( + base_inputs, + is_deposit, + base_pool, + base_pool_lp_token, + base_n_coins + ) @internal From 11c152d388662917c0ed810f31783ce86c850896 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:19:27 +0200 Subject: [PATCH 334/337] use more precise calculations for legacy basepools --- contracts/main/CurveStableSwapNGViews.vy | 86 +++++++++++++++--------- poetry.lock | 30 ++------- pyproject.toml | 2 +- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/contracts/main/CurveStableSwapNGViews.vy b/contracts/main/CurveStableSwapNGViews.vy index 2f352b5c..cde392bc 100644 --- a/contracts/main/CurveStableSwapNGViews.vy +++ b/contracts/main/CurveStableSwapNGViews.vy @@ -8,6 +8,8 @@ integrators """ +from vyper.interfaces import ERC20Detailed + interface StableSwapNG: def N_COINS() -> uint256: view def BASE_POOL() -> address: view @@ -21,6 +23,7 @@ interface StableSwapNG: def calc_withdraw_one_coin(_token_amount: uint256, i: int128) -> uint256: view def totalSupply() -> uint256: view def offpeg_fee_multiplier() -> uint256: view + def coins(i: uint256) -> address: view A_PRECISION: constant(uint256) = 100 MAX_COINS: constant(uint256) = 8 @@ -28,7 +31,9 @@ PRECISION: constant(uint256) = 10 ** 18 FEE_DENOMINATOR: constant(uint256) = 10 ** 10 -VERSION: public(constant(String[8])) = "1.1.0" +VERSION: public(constant(String[8])) = "1.2.0" +# first version was: 0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA (ethereum mainnet) +# second version was: 0x13526206545e2DC7CcfBaF28dC88F440ce7AD3e0 (ethereum mainnet) # ------------------------------ Public Getters ------------------------------ @@ -148,6 +153,7 @@ def get_dy_underlying( N_COINS: uint256 = StableSwapNG(pool).N_COINS() MAX_COIN: int128 = convert(N_COINS, int128) - 1 BASE_POOL: address = StableSwapNG(pool).BASE_POOL() + base_lp_token: address = StableSwapNG(pool).coins(1) rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) @@ -178,7 +184,12 @@ def get_dy_underlying( # i is from BasePool base_n_coins: uint256 = StableSwapNG(pool).BASE_N_COINS() x = self._base_calc_token_amount( - dx, base_i, base_n_coins, BASE_POOL, True + dx, + base_i, + base_n_coins, + BASE_POOL, + base_lp_token, + True, ) * rates[1] / PRECISION # Adding number of pool tokens @@ -382,35 +393,43 @@ def _dynamic_fee(xpi: uint256, xpj: uint256, _fee: uint256, _fee_multiplier: uin @internal @view def _calc_token_amount( - _amounts: uint256[MAX_COINS], - is_deposit: bool, + _amounts: DynArray[uint256, MAX_COINS], + _is_deposit: bool, pool: address, pool_lp_token: address, n_coins: uint256, ) -> uint256: amp: uint256 = StableSwapNG(pool).A() * A_PRECISION - N_COINS: uint256 = StableSwapNG(pool).N_COINS() rates: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) old_balances: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) xp: DynArray[uint256, MAX_COINS] = empty(DynArray[uint256, MAX_COINS]) - pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) + pool_is_ng: bool = raw_call( + pool, + method_id("D_ma_time()"), + revert_on_failure=False, + is_static_call=True + ) + use_dynamic_fees: bool = True if pool_is_ng: - rates, old_balances, xp = self._get_rates_balances_xp(pool, N_COINS) + rates, old_balances, xp = self._get_rates_balances_xp(pool, n_coins) else: - ... # TODO: accommodate if pool is not ng (but: cannot fully accommodate in a simple manner) + use_dynamic_fees = False + for i in range(n_coins, bound=MAX_COINS): + rates.append( + 10 ** (36 - convert(ERC20Detailed(StableSwapNG(pool).coins(i)).decimals(), uint256)) + ) + old_balances.append(StableSwapNG(pool).balances(i)) + xp.append(rates[i] * old_balances[i] / PRECISION) # Initial invariant - D0: uint256 = self.get_D(xp, amp, N_COINS) + D0: uint256 = self.get_D(xp, amp, n_coins) - total_supply: uint256 = StableSwapNG(pool).totalSupply() + total_supply: uint256 = StableSwapNG(pool_lp_token).totalSupply() new_balances: DynArray[uint256, MAX_COINS] = old_balances - for i in range(MAX_COINS): - if i == N_COINS: - break - + for i in range(n_coins, bound=MAX_COINS): amount: uint256 = _amounts[i] if _is_deposit: new_balances[i] += amount @@ -418,27 +437,26 @@ def _calc_token_amount( new_balances[i] -= amount # Invariant after change - for idx in range(MAX_COINS): - if idx == N_COINS: - break + for idx in range(n_coins, bound=MAX_COINS): xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D1: uint256 = self.get_D(xp, amp, N_COINS) + D1: uint256 = self.get_D(xp, amp, n_coins) # We need to recalculate the invariant accounting for fees # to calculate fair user's share D2: uint256 = D1 + fee_multiplier: uint256 = 0 + _dynamic_fee_i: uint256 = 0 if total_supply > 0: # Only account for fees if we are not the first to deposit - base_fee: uint256 = StableSwapNG(pool).fee() * N_COINS / (4 * (N_COINS - 1)) - fee_multiplier: uint256 = StableSwapNG(pool).offpeg_fee_multiplier() - _dynamic_fee_i: uint256 = 0 + base_fee: uint256 = StableSwapNG(pool).fee() * n_coins / (4 * (n_coins - 1)) + if use_dynamic_fees: + fee_multiplier = StableSwapNG(pool).offpeg_fee_multiplier() + xs: uint256 = 0 - ys: uint256 = (D0 + D1) / N_COINS + ys: uint256 = (D0 + D1) / n_coins - for i in range(MAX_COINS): - if i == N_COINS: - break + for i in range(n_coins, bound=MAX_COINS): ideal_balance: uint256 = D1 * old_balances[i] / D0 difference: uint256 = 0 @@ -449,15 +467,18 @@ def _calc_token_amount( difference = new_balance - ideal_balance xs = rates[i] * (old_balances[i] + new_balance) / PRECISION - _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) - new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR - for idx in range(MAX_COINS): - if idx == N_COINS: - break + # use dynamic fees only if pool is NG + if use_dynamic_fees: + _dynamic_fee_i = self._dynamic_fee(xs, ys, base_fee, fee_multiplier) + new_balances[i] -= _dynamic_fee_i * difference / FEE_DENOMINATOR + else: + new_balances[i] -= base_fee * difference / FEE_DENOMINATOR + + for idx in range(n_coins, bound=MAX_COINS): xp[idx] = rates[idx] * new_balances[idx] / PRECISION - D2 = self.get_D(xp, amp, N_COINS) + D2 = self.get_D(xp, amp, n_coins) else: return D1 # Take the dust if there was any @@ -480,8 +501,7 @@ def _base_calc_token_amount( is_deposit: bool, ) -> uint256: - base_pool_is_ng: bool = raw_call(base_pool, method_id("D_ma_time()"), revert_on_failure=False, is_static_call=True) - base_inputs: uint256[MAX_COINS] = empty(uint256[MAX_COINS]) + base_inputs: DynArray[uint256, MAX_COINS] = [0, 0, 0, 0, 0, 0, 0, 0] base_inputs[base_i] = dx return self._calc_token_amount( diff --git a/poetry.lock b/poetry.lock index 69fcafa9..fab4bf8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3135,34 +3135,14 @@ typing-extensions = "*" vyper = ">=0.3.10" [package.extras] +colab = ["ipykernel (>=6.29.4)"] forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/DanielSchiavini/titanoboa" -reference = "zksync" -resolved_reference = "f96fff3450c060b6849a632608377b4477667884" - -[[package]] -name = "titanoboa-zksync" -version = "0.0.1" -description = "A Zksync plugin for the Titanoboa Vyper interpreter" -optional = false -python-versions = "*" -files = [] -develop = false - -[package.dependencies] -titanoboa = {git = "https://github.com/DanielSchiavini/titanoboa", rev = "zksync"} - -[package.extras] -forking-recommended = ["ujson"] - -[package.source] -type = "git" -url = "https://github.com/DanielSchiavini/titanoboa-zksync.git" -reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" -resolved_reference = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49" +url = "https://github.com/vyperlang/titanoboa.git" +reference = "9757133904e2b8c2d79650d5713287749f269df0" +resolved_reference = "9757133904e2b8c2d79650d5713287749f269df0" [[package]] name = "tomli" @@ -3473,4 +3453,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "251db9973fb842214b8fa1401ec565f64f6e82e3aac76755a4a26fab226fca05" +content-hash = "ed178f29e080861b28abc40727265b8dcb4766be69d9dcca562d1ad37effe556" diff --git a/pyproject.toml b/pyproject.toml index 0d8ca2c8..f5c32613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ poetry = "1.5.1" vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" -titanoboa-zksync = {git = "https://github.com/DanielSchiavini/titanoboa-zksync.git", rev = "5f13b427df4b8832fcc16ec1f6d44460f1d04b49"} +titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "9757133904e2b8c2d79650d5713287749f269df0"} [tool.poetry.group.dev.dependencies] black = "22.3.0" From 0b308e070358fa5dc0df5ff39961a587004b1958 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:14:01 +0200 Subject: [PATCH 335/337] finalise deployment --- poetry.lock | 29 +++++- pyproject.toml | 2 +- scripts/deploy_infra.py | 203 +++--------------------------------- scripts/deployment_utils.py | 6 +- scripts/deployments.py | 203 ++++++++++++++++++++++++++++++++++++ 5 files changed, 249 insertions(+), 194 deletions(-) create mode 100644 scripts/deployments.py diff --git a/poetry.lock b/poetry.lock index fab4bf8b..5db33363 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3140,9 +3140,30 @@ forking-recommended = ["ujson"] [package.source] type = "git" -url = "https://github.com/vyperlang/titanoboa.git" -reference = "9757133904e2b8c2d79650d5713287749f269df0" -resolved_reference = "9757133904e2b8c2d79650d5713287749f269df0" +url = "https://github.com/vyperlang/titanoboa" +reference = "master" +resolved_reference = "ea18746a7405b237cea934b678c3ecffc83f951b" + +[[package]] +name = "titanoboa-zksync" +version = "0.0.1" +description = "A Zksync plugin for the Titanoboa Vyper interpreter" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +titanoboa = {git = "https://github.com/vyperlang/titanoboa", rev = "master"} + +[package.extras] +forking-recommended = ["ujson"] + +[package.source] +type = "git" +url = "https://github.com/DanielSchiavini/titanoboa-zksync.git" +reference = "HEAD" +resolved_reference = "126b53f6b7872d6949e4d44d8e95514aa0fb44a2" [[package]] name = "tomli" @@ -3453,4 +3474,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ed178f29e080861b28abc40727265b8dcb4766be69d9dcca562d1ad37effe556" +content-hash = "f8037e6bb9ee4524d8e1ca00d043746a682d20fac5743eaaf3b0f0d1d608d0ad" diff --git a/pyproject.toml b/pyproject.toml index f5c32613..a5df2948 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ poetry = "1.5.1" vyper = "0.3.10" pycryptodome = "^3.18.0" pre-commit = "^3.3.3" -titanoboa = {git = "https://github.com/vyperlang/titanoboa.git", rev = "9757133904e2b8c2d79650d5713287749f269df0"} +titanoboa-zksync = {git = "https://github.com/DanielSchiavini/titanoboa-zksync.git"} [tool.poetry.group.dev.dependencies] black = "22.3.0" diff --git a/scripts/deploy_infra.py b/scripts/deploy_infra.py index 9087bd04..eeed6273 100644 --- a/scripts/deploy_infra.py +++ b/scripts/deploy_infra.py @@ -9,194 +9,16 @@ from eth_account import Account from rich.console import Console as RichConsole +# sys.path.append(".") +from scripts import deployments + ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" logger = RichConsole(file=sys.stdout) -deployments = { - # Ethereum - "ethereum:mainnet": { - "math": "0xc9CBC565A9F4120a2740ec6f64CC24AeB2bB3E5E", - # "views_old": "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA", - "views": "0x13526206545e2DC7CcfBaF28dC88F440ce7AD3e0", - "plain_amm": "0xDCc91f930b42619377C200BA05b7513f2958b202", - "meta_amm": "0xede71F77d7c900dCA5892720E76316C6E575F0F7", - "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "gauge": "0x38D9BdA812da2C68dFC6aDE85A7F7a54E77F8325", - "zap": "", - }, - "ethereum:sepolia": { - "math": "0x2cad7b3e78e10bcbf2cc443ddd69ca8bcc09a758", - # "views": "0x9d3975070768580f755D405527862ee126d0eA08", - "views": "", - "plain_amm": "0xE12374F193f91f71CE40D53E0db102eBaA9098D5", - "meta_amm": "0xB00E89EaBD59cD3254c88E390103Cf17E914f678", - "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", - "zap": "", - }, - # Layer 2 - "arbitrum:mainnet": { - "math": "0xD4a8bd4d59d65869E99f20b642023a5015619B34", - # "views_old": "0x9293f068912bae932843a1bA01806c54f416019D", - "views": "0xDD7EBB1C49780519dD9755B8B1A23a6f42CE099E", - "plain_amm": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", - "meta_amm": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", - "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", - "zap": "", - }, - "optimism:mainnet": { - "math": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - # "views_old": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "views": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", - "plain_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", - "meta_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "", - }, - "base:mainnet": { - "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - # "views_old": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "views": "0xC1b393EfEF38140662b91441C6710Aa704973228", - "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "zap": "", - }, - "linea:mainnet": { - "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", - "plain_amm": "0xa7b9d886a9a374a1c86dc52d2ba585c5cdfdac26", - "meta_amm": "0xf3a6aa40cf048a3960e9664847e9a7be025a390a", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "", - }, - "scroll:mainnet": { - "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", - "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "", - }, - "pzkevm:mainnet": { - "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", - "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "zap": "", - }, - # Layer 1 - "gnosis:mainnet": { - "math": "0xFAbC421e3368D158d802684A217a83c083c94CeB", - # "views_old": "0x0c59d36b23f809f8b6C7cb4c8C590a0AC103baEf", - "views": "0x33e72383472f77B0C6d8F791D1613C75aE2C5915", - "plain_amm": "0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a", - "meta_amm": "0xC1b393EfEF38140662b91441C6710Aa704973228", - "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "zap": "", - }, - "polygon:mainnet": { - "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", - "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "zap": "", - }, - "avax:mainnet": { - "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", - "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "zap": "", - }, - "ftm:mainnet": { - "math": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - # "views_old": "0x635742dCC8313DCf8c904206037d962c042EAfBd", - "views": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", - "plain_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", - "meta_amm": "0x046207cB759F527b6c10C2D61DBaca45513685CC", - "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", - "zap": "", - }, - "bsc:mainnet": { - "math": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", - # "views_old": "0x5Ea9DD3b6f042A34Df818C6c1324BC5A7c61427a", - "views": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", - "plain_amm": "0x505d666E4DD174DcDD7FA090ed95554486d2Be44", - "meta_amm": "0x5a8C93EE12a8Df4455BA111647AdA41f29D5CfcC", - "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "zap": "", - }, - "celo:mainnet": { - "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", - "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "zap": "", - }, - "kava:mainnet": { - "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - # "views_old": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", - "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "zap": "", - }, - "aurora:mainnet": { - "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", - # "views_old": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", - "views": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", - "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", - "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "", - }, - "fraxtal:mainnet": { - "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - # "views_old": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "views": "0xFAbC421e3368D158d802684A217a83c083c94CeB", - "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "meta_amm": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", - "zap": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", - }, - "mantle:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - # "views_old": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", - "views": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "", - "factory_ctor": "000000000000000000000000f3a431008396df8a8b2df492c913706bdb0874ef0000000000000000000000002d12d0907a388811e3aa855a550f959501d303ee", # noqa:E501 - }, - "xlayer:mainnet": { - "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", - "views": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", - "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", - "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", - "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", - "zap": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", - }, - "zksync:mainnet": { - "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", - "views": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", - "plain_amm": "0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4", - "meta_amm": "0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3", - "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", - "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", - }, -} + +def fetch_url(network): + return os.getenv("DRPC_URL") % (network, os.getenv("DRPC_KEY")) def set_contract_pragma(contract_file, network) -> boa.contracts.vyper.vyper_contract.VyperDeployer: @@ -227,7 +49,7 @@ def set_contract_pragma(contract_file, network) -> boa.contracts.vyper.vyper_con def check_and_deploy(contract_obj, contract_designation, network, blueprint: bool = False, args=[]): - deployed_contract = deployments[network][contract_designation] + deployed_contract = deployments.deployments[network][contract_designation] try: contract_name = os.path.basename(contract_obj.filename) except AttributeError: @@ -389,7 +211,16 @@ def main(): deployer = "FIDDYDEPLOYER" fork = False network = "zksync" - rpc = "https://mainnet.era.zksync.io" + + if network == "zksync": + rpc = "https://mainnet.era.zksync.io" + elif network == "fraxtal": + rpc = "https://rpc.frax.com" + elif network == "xlayer": + rpc = "https://rpc.xlayer.tech" + else: + rpc = fetch_url(network) + deploy_infra(f"{network}:mainnet", rpc, deployer, fork=fork) diff --git a/scripts/deployment_utils.py b/scripts/deployment_utils.py index e3463339..434e86be 100644 --- a/scripts/deployment_utils.py +++ b/scripts/deployment_utils.py @@ -61,7 +61,7 @@ class CurveNetworkSettings: dao_ownership_contract="0xCb8799BFF48bb549F7B69Bb9BE60DbA7cd4F1BB7", fee_receiver_address="0xCb8799BFF48bb549F7B69Bb9BE60DbA7cd4F1BB7", ), - "pzkevm:mainnet": CurveNetworkSettings( + "polygon-zkevm:mainnet": CurveNetworkSettings( dao_ownership_contract="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", fee_receiver_address="0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", ), @@ -74,11 +74,11 @@ class CurveNetworkSettings: dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy ), - "avax:mainnet": CurveNetworkSettings( + "avalanche:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0x06534b0BF7Ff378F162d4F348390BDA53b15fA35", ), - "ftm:mainnet": CurveNetworkSettings( + "fantom:mainnet": CurveNetworkSettings( dao_ownership_contract="0xB055EbbAcc8Eefc166c169e9Ce2886D0406aB49b", # proxy fee_receiver_address="0x2B039565B2b7a1A9192D4847fbd33B25b836B950", ), diff --git a/scripts/deployments.py b/scripts/deployments.py new file mode 100644 index 00000000..1aa99e01 --- /dev/null +++ b/scripts/deployments.py @@ -0,0 +1,203 @@ +deployments = { + # Ethereum + "ethereum:mainnet": { + "math": "0xc9CBC565A9F4120a2740ec6f64CC24AeB2bB3E5E", + # "views_old_0": "0xe0B15824862f3222fdFeD99FeBD0f7e0EC26E1FA", + # "views_old_1": "0x13526206545e2DC7CcfBaF28dC88F440ce7AD3e0", + "views": "0xFF53042865dF617de4bB871bD0988E7B93439cCF", + "plain_amm": "0xDCc91f930b42619377C200BA05b7513f2958b202", + "meta_amm": "0xede71F77d7c900dCA5892720E76316C6E575F0F7", + "factory": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", + "gauge": "0x38D9BdA812da2C68dFC6aDE85A7F7a54E77F8325", + "zap": "0xDfeF1725Ab767f165171709C6d1E1A6247425fE0", + }, + "ethereum:sepolia": { + "math": "0x2cad7b3e78e10bcbf2cc443ddd69ca8bcc09a758", + # "views": "0x9d3975070768580f755D405527862ee126d0eA08", + "views": "", + "plain_amm": "0xE12374F193f91f71CE40D53E0db102eBaA9098D5", + "meta_amm": "0xB00E89EaBD59cD3254c88E390103Cf17E914f678", + "factory": "0xfb37b8D939FFa77114005e61CFc2e543d6F49A81", + "zap": "", + }, + # Layer 2 + "arbitrum:mainnet": { + "math": "0xD4a8bd4d59d65869E99f20b642023a5015619B34", + # "views_old_0": "0x9293f068912bae932843a1bA01806c54f416019D", + # "views_old_1": "0xDD7EBB1C49780519dD9755B8B1A23a6f42CE099E", + "views": "", + "plain_amm": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", + "meta_amm": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", + "factory": "0x9AF14D26075f142eb3F292D5065EB3faa646167b", + "zap": "0x59AfCD3e931018dc493AA1d833B11bb5A0744906", + }, + "optimism:mainnet": { + "math": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + # "views_old_0": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + # "views_old_1": "0xf6841C27fe35ED7069189aFD5b81513578AFD7FF", + "views": "0xbC7654d2DD901AaAa3BE4Cb5Bc0f10dEA9f96443", + "plain_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "meta_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0x07920E98a66e462C2Aa4c8fa6200bc68CA161ea0", + }, + "base:mainnet": { + "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old_0": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + # "views_old_1": "0xC1b393EfEF38140662b91441C6710Aa704973228", + "views": "0xA54f3c1DFa5f7DbF2564829d14b3B74a65d26Ae2", + "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "0x3f445D38E820c010a7A6E33c5F80cBEBE6930f61", + }, + "linea:mainnet": { + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_0": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old_1": "0x3E3B5F27bbf5CC967E074b70E9f4046e31663181", + "views": "0xB6845b562F01eB02ef20CBB63553d2a768e5a1Cb", + "plain_amm": "0xa7b9d886a9a374a1c86dc52d2ba585c5cdfdac26", + "meta_amm": "0xf3a6aa40cf048a3960e9664847e9a7be025a390a", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", + }, + "scroll:mainnet": { + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_0": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old_1": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", + "views": "0x3f445D38E820c010a7A6E33c5F80cBEBE6930f61", + "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0xb47988aD49DCE8D909c6f9Cf7B26caF04e1445c8", + }, + "polygon-zkevm:mainnet": { + "math": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old_1": "0x87DD13Dd25a1DBde0E1EdcF5B8Fa6cfff7eABCaD", + "views": "0xB6845b562F01eB02ef20CBB63553d2a768e5a1Cb", + "plain_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "meta_amm": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", + }, + # Layer 1 + "gnosis:mainnet": { + "math": "0xFAbC421e3368D158d802684A217a83c083c94CeB", + # "views_old_0": "0x0c59d36b23f809f8b6C7cb4c8C590a0AC103baEf", + # "views_old_1": "0x33e72383472f77B0C6d8F791D1613C75aE2C5915", + "views": "0xa0EC67a3C483674f77915893346A8CA3AbE2b785", + "plain_amm": "0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a", + "meta_amm": "0xC1b393EfEF38140662b91441C6710Aa704973228", + "factory": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + "zap": "0x08390C76DFDaB74249754C8e71cC2747351bd388", + }, + "polygon:mainnet": { + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + # "views_old_0": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_1": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", + "views": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "0x4c7a5a5d57f98d362f1c00d7135f0da5b6f82227", + }, + "avalanche:mainnet": { + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + # "views_old_0": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_1": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", + "views": "0xe548590f9fAe7a23EA6501b144B0D58b74Fc4B53", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "0xA54f3c1DFa5f7DbF2564829d14b3B74a65d26Ae2", + }, + "fantom:mainnet": { + "math": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + # "views_old_0": "0x635742dCC8313DCf8c904206037d962c042EAfBd", + # "views_old_1": "0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf", + "views": "0x33e72383472f77B0C6d8F791D1613C75aE2C5915", + "plain_amm": "0x5702BDB1Ec244704E3cBBaAE11a0275aE5b07499", + "meta_amm": "0x046207cB759F527b6c10C2D61DBaca45513685CC", + "factory": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + "zap": "0x21688e843a99B0a47E750e7dDD2b5dAFd9269d30", + }, + "bsc:mainnet": { + "math": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", + # "views_old_0": "0x5Ea9DD3b6f042A34Df818C6c1324BC5A7c61427a", + # "views_old_1": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", + "views": "0xbC7654d2DD901AaAa3BE4Cb5Bc0f10dEA9f96443", + "plain_amm": "0x505d666E4DD174DcDD7FA090ed95554486d2Be44", + "meta_amm": "0x5a8C93EE12a8Df4455BA111647AdA41f29D5CfcC", + "factory": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "zap": "0x07920e98a66e462c2aa4c8fa6200bc68ca161ea0", + }, + "celo:mainnet": { + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + # "views_old_0": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_1": "0x8F7632122125699da7E22d465fa16EdE4f687Fa4", + "views": "0xA54f3c1DFa5f7DbF2564829d14b3B74a65d26Ae2", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "0x3f445D38E820c010a7A6E33c5F80cBEBE6930f61", + }, + "kava:mainnet": { + "math": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + # "views_old_0": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_1": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", + "views": "0xB6845b562F01eB02ef20CBB63553d2a768e5a1Cb", + "plain_amm": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + "meta_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "factory": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "zap": "0xf2eff2Cd0d9C82b7b2f17FbBed703fA7931dB1da", + }, + "aurora:mainnet": { + "math": "0xbC0797015fcFc47d9C1856639CaE50D0e69FbEE8", + # "views_old_0": "0xe265FC390E9129b7E337Da23cD42E00C34Da2CE3", + # "views_old_1": "0x20D1c021525C85D9617Ccc64D8f547d5f730118A", + "views": "0xD4a8bd4d59d65869E99f20b642023a5015619B34", + "plain_amm": "0xa7b9d886A9a374A1C86DC52d2BA585c5CDFdac26", + "meta_amm": "0xf3A6aa40cf048a3960E9664847E9a7be025a390a", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0x9293f068912bae932843a1bA01806c54f416019D", + }, + "fraxtal:mainnet": { + "math": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + # "views_old_0": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + # "views_old_1": "0xFAbC421e3368D158d802684A217a83c083c94CeB", + "views": "0xeEcCd039d7228530D5F0c3ce7291Dd9677CCFFb1", + "plain_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "meta_amm": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "factory": "0xd2002373543Ce3527023C75e7518C274A51ce712", + "zap": "0xe61Fb97Ef6eBFBa12B36Ffd7be785c1F5A2DE66b", + }, + "mantle:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + # "views_old_0": "0x506F594ceb4E33F5161139bAe3Ee911014df9f7f", + # "views_old_1": "0x166c4084Ad2434E8F2425C64dabFE6875A0D45c5", + "views": "0xFf02cBD91F57A778Bab7218DA562594a680B8B61", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0xe548590f9fAe7a23EA6501b144B0D58b74Fc4B53", + "factory_ctor": "000000000000000000000000f3a431008396df8a8b2df492c913706bdb0874ef0000000000000000000000002d12d0907a388811e3aa855a550f959501d303ee", # noqa:E501 + }, + "xlayer:mainnet": { + "math": "0x8b3EFBEfa6eD222077455d6f0DCdA3bF4f3F57A6", + # "views_old_1": "0xd7E72f3615aa65b92A4DBdC211E296a35512988B", + "views": "0xb47988aD49DCE8D909c6f9Cf7B26caF04e1445c8", + "plain_amm": "0x87FE17697D0f14A222e8bEf386a0860eCffDD617", + "meta_amm": "0x1764ee18e8B3ccA4787249Ceb249356192594585", + "factory": "0x5eeE3091f747E60a045a2E715a4c71e600e31F6E", + "zap": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", + }, + "zksync:mainnet": { + "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", + # "views_old_1": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", + "views": "0xeF62cD5CBa8B040827B648dBc6a755ddeeb84E65", + "plain_amm": "0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4", + "meta_amm": "0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3", + "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", + "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", + }, +} From b3b19ed44419dbdd6cdb03a1a46aa9540ae55c75 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:02:28 +0200 Subject: [PATCH 336/337] redeploy zksync contracts with new zkvyper --- scripts/deployments.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/deployments.py b/scripts/deployments.py index 1aa99e01..4d1b5780 100644 --- a/scripts/deployments.py +++ b/scripts/deployments.py @@ -192,12 +192,20 @@ "zap": "0x604388Bb1159AFd21eB5191cE22b4DeCdEE2Ae22", }, "zksync:mainnet": { - "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", - # "views_old_1": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", - "views": "0xeF62cD5CBa8B040827B648dBc6a755ddeeb84E65", - "plain_amm": "0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4", - "meta_amm": "0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3", - "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", - "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", + # old zkvyper: + # "math": "0xcf19236e85000901dE2Fad3199aA4A1F74a78B6C", + # # "views_old_1": "0xDD82bEe76CB4b161B44533e4B6Dfc2eee7e066D4", + # "views": "0xeF62cD5CBa8B040827B648dBc6a755ddeeb84E65", + # "plain_amm": "0x3ce3009F8ad07161BA9d02d7A0173180d0281cA4", + # "meta_amm": "0x1E9A82C2a3DF2E0793a2B828aA652Db192f3C8F3", + # "factory": "0x375444aeDEb6C3db897f293E1DBa85D7422A6859", + # "zap": "0x4232Dcc6D31543A2431079BdE2082C69eA3A771E", + # new zkvyper: + "math": "0x29Fc22c7fEC8748a85852E2D36728D9194DDb854", + "views": "0x59557D68d46e8367Fb357F2E848D8506cBf371c9", + "plain_amm": "0x04D0095a1A4Ae881a078ae61F36945E85464e6d7", + "meta_amm": "0xC5d5402481aefec461Ab86b1051AC26dF05BeE3B", + "factory": "0xFcAb5d04e8e031334D5e8D2C166B08daB0BE6CaE", + "zap": "0x1F280a5CFd3220b95819674a635B0D12a32F0E6a", }, } From 75662996cba5bdeeebdd72051c4a0f2ff1591988 Mon Sep 17 00:00:00 2001 From: curvefi Date: Fri, 16 Aug 2024 08:06:02 +0000 Subject: [PATCH 337/337] chore: add release file - zkvyper version 1.5.3 for stableswap --- .../release-zkvyper version 1.5.3 for stableswap-16-08-2024.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 deployments/release-zkvyper version 1.5.3 for stableswap-16-08-2024.txt diff --git a/deployments/release-zkvyper version 1.5.3 for stableswap-16-08-2024.txt b/deployments/release-zkvyper version 1.5.3 for stableswap-16-08-2024.txt new file mode 100644 index 00000000..e227d9e4 --- /dev/null +++ b/deployments/release-zkvyper version 1.5.3 for stableswap-16-08-2024.txt @@ -0,0 +1 @@ +"math": "0x29Fc22c7fEC8748a85852E2D36728D9194DDb854", "views": "0x59557D68d46e8367Fb357F2E848D8506cBf371c9", "plain_amm": "0x04D0095a1A4Ae881a078ae61F36945E85464e6d7", "meta_amm": "0xC5d5402481aefec461Ab86b1051AC26dF05BeE3B", "factory": "0xFcAb5d04e8e031334D5e8D2C166B08daB0BE6CaE", "zap": "0x1F280a5CFd3220b95819674a635B0D12a32F0E6a",