From 6ac381e0a59cdafadfaca21adc418d818e53e19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 12 Nov 2021 13:50:38 +0100 Subject: [PATCH 1/4] tests: switch to ganache v7 --- .github/workflows/test.yml | 3 ++- Makefile | 2 +- tests/test_uniswap.py | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23ac279..5ad7504 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,8 @@ jobs: - name: Install dependencies run: | poetry install - npm install -g ganache-cli + # TODO: Update to stable ganache when released! + npm install -g ganache@alpha - name: Test env: PROVIDER: ${{ secrets.MAINNET_PROVIDER }} diff --git a/Makefile b/Makefile index e8a8eb8..ac1ebe7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: test typecheck lint precommit docs test: - poetry run pytest -v --cov=uniswap --cov-report html --cov-report term --cov-report xml + poetry run pytest -v --tb=line --maxfail=4 --cov=uniswap --cov-report html --cov-report term --cov-report xml typecheck: poetry run mypy --pretty diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index 3764772..b9393db 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -67,10 +67,10 @@ def web3(ganache: GanacheInstance): @pytest.fixture(scope="module") def ganache() -> Generator[GanacheInstance, None, None]: - """Fixture that runs ganache-cli which has forked off mainnet""" - if not shutil.which("ganache-cli"): + """Fixture that runs ganache which has forked off mainnet""" + if not shutil.which("ganache"): raise Exception( - "ganache-cli was not found in PATH, you can install it with `npm install -g ganache-cli`" + "ganache was not found in PATH, you can install it with `npm install -g ganache`" ) if "PROVIDER" not in os.environ: raise Exception( @@ -79,7 +79,7 @@ def ganache() -> Generator[GanacheInstance, None, None]: port = 10999 p = subprocess.Popen( - f"ganache-cli --port {port} -s test --networkId 1 --fork {os.environ['PROVIDER']}", + f"ganache --port {port} -s test --networkId 1 --fork {os.environ['PROVIDER']}", shell=True, ) # Address #1 when ganache is run with `-s test`, it starts with 100 ETH @@ -105,7 +105,7 @@ class TestUniswap(object): ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" # TODO: Detect mainnet vs rinkeby and set accordingly, like _get_token_addresses in the Uniswap class - # For Mainnet testing (with `ganache-cli --fork` as per the ganache fixture) + # For Mainnet testing (with `ganache --fork` as per the ganache fixture) eth = "0x0000000000000000000000000000000000000000" weth = Web3.toChecksumAddress("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") bat = Web3.toChecksumAddress("0x0D8775F648430679A709E98d2b0Cb6250d2887EF") From 5fd95d28d14ffc44d79e771ed098ed4cc9e77f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Wed, 1 Dec 2021 17:08:34 +0100 Subject: [PATCH 2/4] ci: updated to ganache v7 beta --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ad7504..acb725e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: run: | poetry install # TODO: Update to stable ganache when released! - npm install -g ganache@alpha + npm install -g ganache@beta - name: Test env: PROVIDER: ${{ secrets.MAINNET_PROVIDER }} From cdadaf3c4bfe68f6382aeaf0c12916d84c7f2304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Tue, 21 Dec 2021 09:27:23 +0100 Subject: [PATCH 3/4] test: lowered receipt timeout in tests --- tests/test_uniswap.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index b9393db..19ceadf 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -25,6 +25,8 @@ else: UNISWAP_VERSIONS = [1, 2, 3] +RECEIPT_TIMEOUT = 5 + @dataclass class GanacheInstance: @@ -54,12 +56,12 @@ def test_assets(client: Uniswap): logger.info("Buying...") tx = client.make_trade_output(tokens["ETH"], token_addr, amount) - client.w3.eth.wait_for_transaction_receipt(tx) + client.w3.eth.wait_for_transaction_receipt(tx, timeout=RECEIPT_TIMEOUT) @pytest.fixture(scope="module") def web3(ganache: GanacheInstance): - w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 60})) + w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) if 1 != int(w3.net.version): raise Exception("PROVIDER was not a mainnet provider, which the tests require") return w3 @@ -219,7 +221,7 @@ def get_exchange_rate( ) def test_add_liquidity(self, client: Uniswap, web3: Web3, token, max_eth): r = client.add_liquidity(token, max_eth) - tx = web3.eth.wait_for_transaction_receipt(r, timeout=6000) + tx = web3.eth.wait_for_transaction_receipt(r, timeout=RECEIPT_TIMEOUT) assert tx["status"] @pytest.mark.skip @@ -274,7 +276,7 @@ def test_make_trade( bal_in_before = client.get_token_balance(input_token) txid = client.make_trade(input_token, output_token, qty, recipient) - tx = web3.eth.wait_for_transaction_receipt(txid) + tx = web3.eth.wait_for_transaction_receipt(txid, timeout=RECEIPT_TIMEOUT) assert tx["status"] # TODO: Checks for ETH, taking gas into account @@ -324,7 +326,7 @@ def test_make_trade_output( balance_before = client.get_token_balance(output_token) r = client.make_trade_output(input_token, output_token, qty, recipient) - tx = web3.eth.wait_for_transaction_receipt(r, timeout=30) + tx = web3.eth.wait_for_transaction_receipt(r, timeout=RECEIPT_TIMEOUT) assert tx["status"] # TODO: Checks for ETH, taking gas into account From 2a5c791d566c04893cdf993438ffc4757e45e053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Tue, 21 Dec 2021 10:35:32 +0100 Subject: [PATCH 4/4] test: fixed tests --- pyproject.toml | 4 +++ tests/test_uniswap.py | 22 ++++++++++-- uniswap/uniswap.py | 79 ++++++++++++++++++++++++++++--------------- 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 20d3052..be139bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,10 @@ Sphinx = "*" sphinx-book-theme = "*" sphinx-click = "*" +[tool.pytest.ini_options] +log_cli = false # to print logs during tests, set to true +#log_level = "NOTSET" + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index 19ceadf..08ea0b7 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -18,6 +18,7 @@ logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) ENV_UNISWAP_VERSION = os.getenv("UNISWAP_VERSION", None) if ENV_UNISWAP_VERSION: @@ -38,7 +39,11 @@ class GanacheInstance: @pytest.fixture(scope="module", params=UNISWAP_VERSIONS) def client(request, web3: Web3, ganache: GanacheInstance): return Uniswap( - ganache.eth_address, ganache.eth_privkey, web3=web3, version=request.param + ganache.eth_address, + ganache.eth_privkey, + web3=web3, + version=request.param, + use_estimate_gas=False, # see note in _build_and_send_tx ) @@ -80,11 +85,22 @@ def ganache() -> Generator[GanacheInstance, None, None]: ) port = 10999 + defaultGasPrice = 1000_000_000_000 # 1000 gwei p = subprocess.Popen( - f"ganache --port {port} -s test --networkId 1 --fork {os.environ['PROVIDER']}", + f"""ganache + --port {port} + --wallet.seed test + --chain.networkId 1 + --chain.chainId 1 + --fork.url {os.environ['PROVIDER']} + --miner.defaultGasPrice {defaultGasPrice} + --miner.legacyInstamine true + """.replace( + "\n", " " + ), shell=True, ) - # Address #1 when ganache is run with `-s test`, it starts with 100 ETH + # Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH eth_address = "0x94e3361495bD110114ac0b6e35Ed75E77E6a6cFA" eth_privkey = "0x6f1313062db38875fb01ee52682cbf6a8420e92bfbc578c5d4fdc0a32c50266f" sleep(3) diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index 6c54b70..fb543d6 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -45,6 +45,16 @@ class Uniswap: Wrapper around Uniswap contracts. """ + address: AddressLike + version: int + + w3: Web3 + netid: int + netname: str + + default_slippage: float + use_estimate_gas: bool + def __init__( self, address: Union[AddressLike, str, None], @@ -53,6 +63,8 @@ def __init__( web3: Web3 = None, version: int = 1, default_slippage: float = 0.01, + use_estimate_gas: bool = True, + # use_eip1559: bool = True, factory_contract_addr: str = None, router_contract_addr: str = None, ) -> None: @@ -66,7 +78,7 @@ def __init__( :param factory_contract_addr: Can be optionally set to override the address of the factory contract. :param router_contract_addr: Can be optionally set to override the address of the router contract (v2 only). """ - self.address: AddressLike = _str_to_addr( + self.address = _str_to_addr( address or "0x0000000000000000000000000000000000000000" ) self.private_key = ( @@ -78,22 +90,23 @@ def __init__( # TODO: Write tests for slippage self.default_slippage = default_slippage + self.use_estimate_gas = use_estimate_gas if web3: self.w3 = web3 else: # Initialize web3. Extra provider for testing. - self.provider = provider or os.environ["PROVIDER"] - self.w3 = Web3( - Web3.HTTPProvider(self.provider, request_kwargs={"timeout": 60}) - ) - - netid = int(self.w3.net.version) - if netid in _netid_to_name: - self.network = _netid_to_name[netid] + if not provider: + provider = os.environ["PROVIDER"] + self.w3 = Web3(Web3.HTTPProvider(provider, request_kwargs={"timeout": 60})) + + # Cache netid to avoid extra RPC calls + self.netid = int(self.w3.net.version) + if self.netid in _netid_to_name: + self.netname = _netid_to_name[self.netid] else: - raise Exception(f"Unknown netid: {netid}") - logger.info(f"Using {self.w3} ('{self.network}')") + raise Exception(f"Unknown netid: {self.netid}") + logger.info(f"Using {self.w3} ('{self.netname}', netid: {self.netid})") self.last_nonce: Nonce = self.w3.eth.get_transaction_count(self.address) @@ -102,14 +115,14 @@ def __init__( # max_approval_check checks that current approval is above a reasonable number # The program cannot check for max_approval each time because it decreases # with each trade. - self.max_approval_hex = f"0x{64 * 'f'}" - self.max_approval_int = int(self.max_approval_hex, 16) - self.max_approval_check_hex = f"0x{15 * '0'}{49 * 'f'}" - self.max_approval_check_int = int(self.max_approval_check_hex, 16) + max_approval_hex = f"0x{64 * 'f'}" + self.max_approval_int = int(max_approval_hex, 16) + max_approval_check_hex = f"0x{15 * '0'}{49 * 'f'}" + self.max_approval_check_int = int(max_approval_check_hex, 16) if self.version == 1: if factory_contract_addr is None: - factory_contract_addr = _factory_contract_addresses_v1[self.network] + factory_contract_addr = _factory_contract_addresses_v1[self.netname] self.factory_contract = _load_contract( self.w3, @@ -118,11 +131,11 @@ def __init__( ) elif self.version == 2: if router_contract_addr is None: - router_contract_addr = _router_contract_addresses_v2[self.network] + router_contract_addr = _router_contract_addresses_v2[self.netname] self.router_address: AddressLike = _str_to_addr(router_contract_addr) if factory_contract_addr is None: - factory_contract_addr = _factory_contract_addresses_v2[self.network] + factory_contract_addr = _factory_contract_addresses_v2[self.netname] self.factory_contract = _load_contract( self.w3, abi_name="uniswap-v2/factory", @@ -1085,8 +1098,19 @@ def _build_and_send_tx( if not tx_params: tx_params = self._get_tx_params() transaction = function.buildTransaction(tx_params) - # Uniswap3 uses 20% margin for transactions - transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) + + if "gas" not in tx_params: + # `use_estimate_gas` needs to be True for networks like Arbitrum (can't assume 250000 gas), + # but it breaks tests for unknown reasons because estimateGas takes forever on some tx's. + # Maybe an issue with ganache? (got GC warnings once...) + if self.use_estimate_gas: + # The Uniswap V3 UI uses 20% margin for transactions + transaction["gas"] = Wei( + int(self.w3.eth.estimate_gas(transaction) * 1.2) + ) + else: + transaction["gas"] = Wei(250000) + signed_txn = self.w3.eth.account.sign_transaction( transaction, private_key=self.private_key ) @@ -1098,15 +1122,18 @@ def _build_and_send_tx( logger.debug(f"nonce: {tx_params['nonce']}") self.last_nonce = Nonce(tx_params["nonce"] + 1) - def _get_tx_params(self, value: Wei = Wei(0)) -> TxParams: + def _get_tx_params(self, value: Wei = Wei(0), gas: Wei = None) -> TxParams: """Get generic transaction parameters.""" - return { + params: TxParams = { "from": _addr_to_str(self.address), "value": value, "nonce": max( self.last_nonce, self.w3.eth.get_transaction_count(self.address) ), } + if gas: + params["gas"] = gas + return params # ------ Price Calculation Utils --------------------------------------------------- def _calculate_max_input_token( @@ -1255,14 +1282,12 @@ def _get_token_addresses(self) -> Dict[str, ChecksumAddress]: Returns a dict with addresses for tokens for the current net. Used in testing. """ - netid = int(self.w3.net.version) - netname = _netid_to_name[netid] - if netname == "mainnet": + if self.netname == "mainnet": return tokens - elif netname == "rinkeby": + elif self.netname == "rinkeby": return tokens_rinkeby else: - raise Exception(f"Unknown net '{netname}'") + raise Exception(f"Unknown net '{self.netname}'") # ---- Old v1 utils ---- pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy