From 7caaf2471f8d35ddf2ceb42fceb5c4d7a3bc1352 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Mon, 20 May 2024 21:44:32 +0200 Subject: [PATCH 1/2] fix: add revert check for dynarray self.markets in twocryptofactory while calling factory.find_pool_for_coins() --- contracts/mocks/CurveTwocryptoFactory.vy | 484 ++++++++++++++++++ .../ng/CurveTwocryptoFactoryHandler.vy | 27 +- 2 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 contracts/mocks/CurveTwocryptoFactory.vy diff --git a/contracts/mocks/CurveTwocryptoFactory.vy b/contracts/mocks/CurveTwocryptoFactory.vy new file mode 100644 index 0000000..6c1f754 --- /dev/null +++ b/contracts/mocks/CurveTwocryptoFactory.vy @@ -0,0 +1,484 @@ +# pragma version 0.3.10 +# pragma optimize gas +# pragma evm-version paris +""" +@title CurveTwocryptoFactory +@author Curve.Fi +@license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved +@notice Permissionless 2-coin cryptoswap pool deployer and registry +""" + +interface TwocryptoPool: + def balances(i: uint256) -> uint256: view + +interface ERC20: + def decimals() -> uint256: view + + +event TwocryptoPoolDeployed: + pool: address + name: String[64] + symbol: String[32] + coins: address[N_COINS] + math: address + salt: bytes32 + precisions: uint256[N_COINS] + packed_A_gamma: uint256 + packed_fee_params: uint256 + packed_rebalancing_params: uint256 + packed_prices: uint256 + deployer: address + + +event LiquidityGaugeDeployed: + pool: address + gauge: address + +event UpdateFeeReceiver: + _old_fee_receiver: address + _new_fee_receiver: address + +event UpdatePoolImplementation: + _implemention_id: uint256 + _old_pool_implementation: address + _new_pool_implementation: address + +event UpdateGaugeImplementation: + _old_gauge_implementation: address + _new_gauge_implementation: address + +event UpdateMathImplementation: + _old_math_implementation: address + _new_math_implementation: address + +event UpdateViewsImplementation: + _old_views_implementation: address + _new_views_implementation: address + +event TransferOwnership: + _old_owner: address + _new_owner: address + + +struct PoolArray: + liquidity_gauge: address + coins: address[N_COINS] + decimals: uint256[N_COINS] + implementation: address + + +N_COINS: constant(uint256) = 2 +A_MULTIPLIER: constant(uint256) = 10000 + +# Limits +MAX_FEE: constant(uint256) = 10 * 10 ** 9 + +deployer: address +admin: public(address) +future_admin: public(address) + +# fee receiver for all pools: +fee_receiver: public(address) + +pool_implementations: public(HashMap[uint256, address]) +gauge_implementation: public(address) +views_implementation: public(address) +math_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, DynArray[address, 4294967296]] +pool_data: HashMap[address, PoolArray] +pool_list: public(DynArray[address, 4294967296]) # master list of pools + + +@external +def __init__(): + self.deployer = tx.origin + + +@external +def initialise_ownership(_fee_receiver: address, _admin: address): + + assert msg.sender == self.deployer + assert self.admin == empty(address) + + self.fee_receiver = _fee_receiver + self.admin = _admin + + log UpdateFeeReceiver(empty(address), _fee_receiver) + log TransferOwnership(empty(address), _admin) + + +@internal +@pure +def _pack_3(x: uint256[3]) -> uint256: + """ + @notice Packs 3 integers with values <= 10**18 into a uint256 + @param x The uint256[3] to pack + @return The packed uint256 + """ + return (x[0] << 128) | (x[1] << 64) | x[2] + + +@pure +@internal +def _pack_2(p1: uint256, p2: uint256) -> uint256: + return p1 | (p2 << 128) + + +# <--- Pool Deployers ---> + +@external +def deploy_pool( + _name: String[64], + _symbol: String[32], + _coins: address[N_COINS], + implementation_id: uint256, + A: uint256, + gamma: uint256, + mid_fee: uint256, + out_fee: uint256, + fee_gamma: uint256, + allowed_extra_profit: uint256, + adjustment_step: uint256, + ma_exp_time: uint256, + initial_price: uint256, +) -> address: + """ + @notice Deploy a new pool + @param _name Name of the new plain pool + @param _symbol Symbol for the new plain pool - will be concatenated with factory symbol + + @return Address of the deployed pool + """ + pool_implementation: address = self.pool_implementations[implementation_id] + _math_implementation: address = self.math_implementation + assert pool_implementation != empty(address), "Pool implementation not set" + assert _math_implementation != empty(address), "Math implementation not set" + + assert mid_fee < MAX_FEE-1 # mid_fee can be zero + assert out_fee >= mid_fee + assert out_fee < MAX_FEE-1 + assert fee_gamma < 10**18+1 + assert fee_gamma > 0 + + assert allowed_extra_profit < 10**18+1 + + assert adjustment_step < 10**18+1 + assert adjustment_step > 0 + + assert ma_exp_time < 872542 # 7 * 24 * 60 * 60 / ln(2) + assert ma_exp_time > 86 # 60 / ln(2) + + assert initial_price > 10**6 and initial_price < 10**30 # dev: initial price out of bound + + assert _coins[0] != _coins[1], "Duplicate coins" + + decimals: uint256[N_COINS] = empty(uint256[N_COINS]) + precisions: uint256[N_COINS] = empty(uint256[N_COINS]) + for i in range(N_COINS): + d: uint256 = ERC20(_coins[i]).decimals() + assert d < 19, "Max 18 decimals for coins" + decimals[i] = d + precisions[i] = 10 ** (18 - d) + + # pack precision + packed_precisions: uint256 = self._pack_2(precisions[0], precisions[1]) + + # pack fees + packed_fee_params: uint256 = self._pack_3( + [mid_fee, out_fee, fee_gamma] + ) + + # pack liquidity rebalancing params + packed_rebalancing_params: uint256 = self._pack_3( + [allowed_extra_profit, adjustment_step, ma_exp_time] + ) + + # pack gamma and A + packed_gamma_A: uint256 = self._pack_2(gamma, A) + + # pool is an ERC20 implementation + _salt: bytes32 = block.prevhash + pool: address = create_from_blueprint( + pool_implementation, # blueprint: address + _name, # String[64] + _symbol, # String[32] + _coins, # address[N_COINS] + _math_implementation, # address + _salt, # bytes32 + packed_precisions, # uint256 + packed_gamma_A, # uint256 + packed_fee_params, # uint256 + packed_rebalancing_params, # uint256 + initial_price, # uint256 + code_offset=3, + ) + + # populate pool data + self.pool_list.append(pool) + + self.pool_data[pool].decimals = decimals + self.pool_data[pool].coins = _coins + self.pool_data[pool].implementation = pool_implementation + + # add coins to market: + self._add_coins_to_market(_coins[0], _coins[1], pool) + + log TwocryptoPoolDeployed( + pool, + _name, + _symbol, + _coins, + _math_implementation, + _salt, + precisions, + packed_gamma_A, + packed_fee_params, + packed_rebalancing_params, + initial_price, + msg.sender, + ) + + return pool + + +@internal +def _add_coins_to_market(coin_a: address, coin_b: address, pool: address): + + key: uint256 = ( + convert(coin_a, uint256) ^ convert(coin_b, uint256) + ) + self.markets[key].append(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" + assert self.gauge_implementation != empty(address), "Gauge implementation not set" + + gauge: address = create_from_blueprint(self.gauge_implementation, _pool, code_offset=3) + self.pool_data[_pool].liquidity_gauge = gauge + + log LiquidityGaugeDeployed(_pool, gauge) + return gauge + + +# <--- Admin / Guarded Functionality ---> + + +@external +def set_fee_receiver(_fee_receiver: address): + """ + @notice Set fee receiver + @param _fee_receiver Address that fees are sent to + """ + assert msg.sender == self.admin, "dev: admin only" + + log UpdateFeeReceiver(self.fee_receiver, _fee_receiver) + self.fee_receiver = _fee_receiver + + +@external +def set_pool_implementation( + _pool_implementation: address, _implementation_index: uint256 +): + """ + @notice Set pool implementation + @dev Set to empty(address) to prevent deployment of new pools + @param _pool_implementation Address of the new pool implementation + @param _implementation_index Index of the pool implementation + """ + assert msg.sender == self.admin, "dev: admin only" + + log UpdatePoolImplementation( + _implementation_index, + self.pool_implementations[_implementation_index], + _pool_implementation + ) + + self.pool_implementations[_implementation_index] = _pool_implementation + + +@external +def set_gauge_implementation(_gauge_implementation: address): + """ + @notice Set gauge implementation + @dev Set to empty(address) to prevent deployment of new gauges + @param _gauge_implementation Address of the new token implementation + """ + assert msg.sender == self.admin, "dev: admin only" + + log UpdateGaugeImplementation(self.gauge_implementation, _gauge_implementation) + self.gauge_implementation = _gauge_implementation + + +@external +def set_views_implementation(_views_implementation: address): + """ + @notice Set views contract implementation + @param _views_implementation Address of the new views contract + """ + assert msg.sender == self.admin, "dev: admin only" + + log UpdateViewsImplementation(self.views_implementation, _views_implementation) + self.views_implementation = _views_implementation + + +@external +def set_math_implementation(_math_implementation: address): + """ + @notice Set math implementation + @param _math_implementation Address of the new math contract + """ + assert msg.sender == self.admin, "dev: admin only" + + log UpdateMathImplementation(self.math_implementation, _math_implementation) + self.math_implementation = _math_implementation + + +@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 + """ + assert msg.sender == self.future_admin, "dev: future admin only" + + log TransferOwnership(self.admin, msg.sender) + self.admin = msg.sender + + +# <--- Factory Getters ---> + + +@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 pool_count() -> uint256: + """ + @notice Get number of pools deployed from the factory + @return Number of pools deployed from factory + """ + return len(self.pool_list) + + +@view +@external +def get_coins(_pool: address) -> address[N_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_decimals(_pool: address) -> uint256[N_COINS]: + """ + @notice Get decimal places for each coin within a pool + @param _pool Pool address + @return uint256 list of decimals + """ + return self.pool_data[_pool].decimals + + +@view +@external +def get_balances(_pool: address) -> uint256[N_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 + """ + return [TwocryptoPool(_pool).balances(0), TwocryptoPool(_pool).balances(1)] + + +@view +@external +def get_coin_indices( + _pool: address, + _from: address, + _to: address +) -> (uint256, uint256): + """ + @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 uint256 `i`, uint256 `j` + """ + coins: address[2] = self.pool_data[_pool].coins + + if _from == coins[0] and _to == coins[1]: + return 0, 1 + elif _from == coins[1] and _to == coins[0]: + return 1, 0 + else: + raise "Coins not found" + + +@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_market_counts(coin_a: address, coin_b: address) -> uint256: + """ + @notice Gets the number of markets with the specified coins. + @return Number of pools with the input coins + """ + + key: uint256 = ( + convert(coin_a, uint256) ^ convert(coin_b, uint256) + ) + + return len(self.markets[key]) diff --git a/contracts/registry_handlers/ng/CurveTwocryptoFactoryHandler.vy b/contracts/registry_handlers/ng/CurveTwocryptoFactoryHandler.vy index 3878c60..704ed3c 100644 --- a/contracts/registry_handlers/ng/CurveTwocryptoFactoryHandler.vy +++ b/contracts/registry_handlers/ng/CurveTwocryptoFactoryHandler.vy @@ -134,6 +134,31 @@ def _is_registered(_pool: address) -> bool: return self._get_coins(_pool)[0] != empty(address) +@internal +@view +def _find_pool_for_coins(_from: address, _to: address, i: uint256) -> address: + + success: bool = False + response: Bytes[32] = b"" + success, response = raw_call( + self.base_registry.address, + concat( + method_id("find_pool_for_coins(address,address,uint256)"), + convert(_from, bytes32), + convert(_to, bytes32), + convert(i, bytes32) + ), + max_outsize=32, + revert_on_failure=False, + is_static_call=True + ) + + if success: + return convert(response, address) + + return empty(address) + + # ---- view methods (API) of the contract ---- # @external @view @@ -148,7 +173,7 @@ def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address @param i Index of the pool to return @return Address of the pool """ - return self.base_registry.find_pool_for_coins(_from, _to, i) + return self._find_pool_for_coins(_from, _to, i) @external From 681eb5fc8bc69bce64aeb54b723f2e7f37aec9f2 Mon Sep 17 00:00:00 2001 From: bout3fiddy <11488427+bout3fiddy@users.noreply.github.com> Date: Tue, 21 May 2024 18:41:22 +0200 Subject: [PATCH 2/2] replace twocrypto-ng factory handlers: --- requirements.in | 2 +- scripts/replace_twocrypto_handler.py | 87 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 scripts/replace_twocrypto_handler.py diff --git a/requirements.in b/requirements.in index 1dd8c9d..3267103 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ -titanoboa[forking-recommended] @ git+https://github.com/vyperlang/titanoboa@69c68d6dbb60b8f8782b5bfca336f5dc838a7719 +titanoboa[forking-recommended] @ git+https://github.com/vyperlang/titanoboa@05ec84ad1decfe23cd4a3ffc588d567fd322a509 black flake8 isort diff --git a/scripts/replace_twocrypto_handler.py b/scripts/replace_twocrypto_handler.py new file mode 100644 index 0000000..2a88a11 --- /dev/null +++ b/scripts/replace_twocrypto_handler.py @@ -0,0 +1,87 @@ +import os + +import boa +from eth_account import Account +from rich.console import Console as RichConsole + +# import sys +# sys.path.append("./") + +FIDDY_DEPLOYER = "" +ADDRESS_PROVIDER = "0x5ffe7FB82894076ECB99A30D6A32e969e6e35E98" + + +def fetch_url(network): + return os.getenv("DRPC_URL") % (network, os.getenv("DRPC_KEY")) + + +def main(network, fork, url=""): + """ + Deploy the AddressProvider to the network. + """ + + console = RichConsole() + if not url: + url = fetch_url(network) + + console.log(f"Network: {network}") + + if not fork: + # Prodmode + console.log("Running script in prod mode...") + boa.set_network_env(url) + boa.env.add_account(Account.from_key(os.environ["FIDDYDEPLOYER"])) + boa.env.suppress_debug_tt(True) + + else: + # Forkmode + console.log("Simulation Mode. Writing to mainnet-fork.") + boa.env.fork(url=url) + boa.env.eoa = FIDDY_DEPLOYER + + address_provider = boa.load_partial("contracts/AddressProviderNG.vy").at( + ADDRESS_PROVIDER + ) + metaregistry_address = address_provider.get_address(7) + + if "ethereum" not in network: + metaregistry = boa.load_partial("contracts/MetaregistryL2.vy").at( + metaregistry_address + ) + else: + metaregistry = boa.load_partial("contracts/Metaregistry.vy").at( + metaregistry_address + ) + + # set up twocrypto ng factory handler + console.log("Deploy Replacement Twocrypto Factory Handler ...") + twocrypto_ng_factory = address_provider.get_address(13) + twocrypto_ng_factory_handler = boa.load( + "contracts/registry_handlers/ng/CurveTwocryptoFactoryHandler.vy", + twocrypto_ng_factory, + ) + + # Update registry handler in the metaregistry + if "ethereum" not in network: + twocrypto_index = metaregistry.registry_length() - 2 + else: + twocrypto_index = metaregistry.registry_length() - 1 + + console.log("Replace TwocryptoFactoryHandler in Metaregistry ...") + metaregistry.update_registry_handler( + twocrypto_index, twocrypto_ng_factory_handler.address + ) + + # add metaregistry to address provider + console.log("TwocryptoFactoryHandler in Metaregistry replaced.") + + +if __name__ == "__main__": + # not deployed: + # aurora + + fork = False + url = "" + network = "" + + main(network, fork, url=url)