From e9c0bfd00a360cf3efdd8b7eb5fa0d2ee141737b Mon Sep 17 00:00:00 2001 From: Mikko Ohtamaa Date: Wed, 24 Jan 2024 20:28:23 +0100 Subject: [PATCH] Enzyme vault cumulative slippage tolerance test (#187) - A manual test for Enzyme's cumulative slippage tolerance policy --- eth_defi/enzyme/erc20.py | 61 ++++++++++ scripts/enzyme/cumulative-slippage-test.py | 119 +++++++++++++++++++ tests/enzyme/test_guard_enzyme_uniswap_v2.py | 5 +- tests/rpc/test_out_of_gas.py | 2 +- 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 scripts/enzyme/cumulative-slippage-test.py diff --git a/eth_defi/enzyme/erc20.py b/eth_defi/enzyme/erc20.py index 568db72a..13d16d7f 100644 --- a/eth_defi/enzyme/erc20.py +++ b/eth_defi/enzyme/erc20.py @@ -126,3 +126,64 @@ def prepare_approve( ) return bound_call + + +def prepare_transfer_sneaky( + enzyme: EnzymeDeployment, + vault: Vault, + generic_adapter: Contract, + token: Contract, + receiver: HexAddress | str, + amount: int +) -> ContractFunction: + """Prepare an ERC-20 transfer out from the Enzyme vault. + + - Tells the Enzyme vault to move away some tokes + + - Should be blocked by GuardV0, only useable by governance + + :param enzyme: + Enzyme deploymeent + + :param vault: + Vault that needs to perform the swap + + :param generic_adapter: + GenericAdapter contract we use for swaps + + :param token: + ERC-20 token we send + + :param receiver: + The receiver of tokens + + :param amount: + Token amount, raw + + :return: + Transaction object that can be signed and executed + """ + + spent = int(amount * 0.05) + + # Prepare the swap parameters + spend_asset_amounts = [amount] + spend_assets = [token.address] + incoming_assets = [token.address] + min_incoming_assets_amounts = [amount - spent - 10_000] + + # The vault performs a swap on Uniswap v2 + encoded_transfer = encode_function_call(token.functions.transfer, [receiver, spent]) + + bound_call = execute_calls_for_generic_adapter( + comptroller=vault.comptroller, + external_calls=((token, encoded_transfer),), + generic_adapter=generic_adapter, + incoming_assets=incoming_assets, + integration_manager=enzyme.contracts.integration_manager, + min_incoming_asset_amounts=min_incoming_assets_amounts, + spend_asset_amounts=spend_asset_amounts, + spend_assets=spend_assets, + ) + + return bound_call diff --git a/scripts/enzyme/cumulative-slippage-test.py b/scripts/enzyme/cumulative-slippage-test.py new file mode 100644 index 00000000..25a3f0f1 --- /dev/null +++ b/scripts/enzyme/cumulative-slippage-test.py @@ -0,0 +1,119 @@ +"""Test cumulative slippage tolerance of Enzyme vault.""" +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from web3.middleware import construct_sign_and_send_raw_middleware + +from eth_defi.abi import get_deployed_contract, encode_function_call +from eth_defi.enzyme.deployment import EnzymeDeployment, POLYGON_DEPLOYMENT +from eth_defi.enzyme.generic_adapter import execute_calls_for_generic_adapter +from eth_defi.enzyme.vault import Vault +from eth_defi.provider.multi_provider import create_multi_provider_web3 +from eth_defi.token import fetch_erc20_details +from eth_defi.uniswap_v2.deployment import FOREVER_DEADLINE, fetch_deployment + +vault_address = os.environ["VAULT"] +json_rpc_url = os.environ["JSON_RPC_POLYGON"] +private_key = os.environ["PRIVATE_KEY"] +receiver = "0x7612A94AafF7a552C373e3124654C1539a4486A8" + +web3 = create_multi_provider_web3(json_rpc_url) +account: LocalAccount = Account.from_key(private_key) +web3.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) + +deployment = EnzymeDeployment.fetch_deployment(web3, POLYGON_DEPLOYMENT) +vault = Vault.fetch(web3, vault_address) + +# print("Deploying") +# generic_adapter = deploy_contract( +# web3, +# f"VaultSpecificGenericAdapter.json", +# account.address, +# deployment.contracts.integration_manager.address, +# vault.address, +# ) +# print(f"Generic adapter is {generic_adapter.address}") + +generic_adapter = get_deployed_contract( + web3, + f"VaultSpecificGenericAdapter.json", + "0x8C35a027FE7986FA5736813869C0A2A7A991BEDd" +) +vault.generic_adapter = generic_adapter + +wmatic = fetch_erc20_details(web3, "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270").contract +# balance = wmatic.contract.functions.balanceOf(vault.address).call() +# balance = 1 + +# USDC +usdc = fetch_erc20_details(web3, "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174").contract +balance = int(usdc.functions.balanceOf(vault.address).call()) + +# bound_transfer = prepare_transfer_sneaky( +# deployment, +# vault, +# generic_adapter, +# token.contract, +# receiver, +# balance, +# ) + +swap_amount = balance +token_in = usdc +token_out = wmatic + +uniswap_v2 = fetch_deployment( + web3, + "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32", + "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff", + "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f", +) + +# Prepare the swap parameters +token_in_swap_amount = swap_amount +spend_asset_amounts = [token_in_swap_amount] +spend_assets = [token_in] +path = [token_in.address, token_out.address] +expected_outgoing_amount, expected_incoming_amount = uniswap_v2.router.functions.getAmountsOut(token_in_swap_amount, path).call() +extra_slippage = int(expected_incoming_amount * 0.05) +expected_incoming_amount -= extra_slippage +incoming_assets = [token_out] +min_incoming_assets_amounts = [expected_incoming_amount] + +# The vault performs a swap on Uniswap v2 +encoded_approve = encode_function_call(token_in.functions.approve, [uniswap_v2.router.address, token_in_swap_amount]) + +# fmt: off +encoded_swapExactTokensForTokens = encode_function_call( + uniswap_v2.router.functions.swapExactTokensForTokens, + [token_in_swap_amount, 1, path, generic_adapter.address, FOREVER_DEADLINE] +) + +transfer_spent = extra_slippage - 5 + +encoded_transfer = encode_function_call(wmatic.functions.transfer, [receiver, transfer_spent]) + +bound_call = execute_calls_for_generic_adapter( + comptroller=vault.comptroller, + external_calls=( + (token_in, encoded_approve), + (uniswap_v2.router, encoded_swapExactTokensForTokens), + (wmatic, encoded_transfer), + ), + generic_adapter=generic_adapter, + incoming_assets=incoming_assets, + integration_manager=deployment.contracts.integration_manager, + min_incoming_asset_amounts=min_incoming_assets_amounts, + spend_asset_amounts=spend_asset_amounts, + spend_assets=spend_assets, +) + + +tx_hash = bound_call.transact({"from": account.address}) +print("Broadcasting", tx_hash.hex()) +receipt = web3.eth.wait_for_transaction_receipt(tx_hash) + +print("Receipt", receipt) + + diff --git a/tests/enzyme/test_guard_enzyme_uniswap_v2.py b/tests/enzyme/test_guard_enzyme_uniswap_v2.py index 8aec5913..d3e5745b 100644 --- a/tests/enzyme/test_guard_enzyme_uniswap_v2.py +++ b/tests/enzyme/test_guard_enzyme_uniswap_v2.py @@ -17,11 +17,11 @@ - transferWithAuthorization() and receiveWithAuthorization() integration tests for Enzyme protocol """ -import flaky + import pytest from eth_account import Account from eth_account.signers.local import LocalAccount -from eth_typing import HexAddress, ChecksumAddress +from eth_typing import HexAddress from web3 import Web3 from web3.contract import Contract @@ -31,7 +31,6 @@ from eth_defi.middleware import construct_sign_and_send_raw_middleware_anvil from eth_defi.token import TokenDetails from eth_defi.trace import assert_transaction_success_with_explanation, TransactionAssertionError -from eth_defi.usdc.deployment import deploy_fiat_token from eth_defi.usdc.eip_3009 import make_eip_3009_transfer, EIP3009AuthorizationType diff --git a/tests/rpc/test_out_of_gas.py b/tests/rpc/test_out_of_gas.py index 91454706..98687b85 100644 --- a/tests/rpc/test_out_of_gas.py +++ b/tests/rpc/test_out_of_gas.py @@ -26,7 +26,7 @@ def web3(): @pytest.fixture(scope="module") def quickswap(web3) -> UniswapV2Deployment: - """Fetch live quickswap v3 deployment. + """Fetch live quickswap deployment. See https://docs.quickswap.exchange/concepts/protocol-overview/03-smart-contracts for more information """