Source code for eth_defi.enzyme.generic_adapter_vault

"""Safe deployment of Enzyme vaults with generic adapter. """
import logging
import os
from pathlib import Path
from typing import Collection

from eth_typing import HexAddress
from web3.contract import Contract

from eth_defi.enzyme.deployment import EnzymeDeployment
from eth_defi.enzyme.policy import create_safe_default_policy_configuration_for_generic_adapter
from eth_defi.enzyme.vault import Vault
from eth_defi.foundry.forge import deploy_contract_with_forge
from eth_defi.hotwallet import HotWallet
from eth_defi.token import TokenDetails, fetch_erc20_details
from eth_defi.trace import assert_transaction_success_with_explanation
from eth_defi.uniswap_v2.constants import UNISWAP_V2_DEPLOYMENTS, QUICKSWAP_DEPLOYMENTS
from eth_defi.uniswap_v2.utils import ZERO_ADDRESS
from eth_defi.uniswap_v3.constants import UNISWAP_V3_DEPLOYMENTS

logger = logging.getLogger(__name__)


CONTRACTS_ROOT = Path(os.path.dirname(__file__)) / ".." / ".." / "contracts"


[docs]def deploy_vault_with_generic_adapter( deployment: EnzymeDeployment, deployer: HotWallet, asset_manager: HexAddress | str, owner: HexAddress | str, denomination_asset: Contract, terms_of_service: Contract | None, fund_name="Example Fund", fund_symbol="EXAMPLE", whitelisted_assets: Collection[TokenDetails] | None = None, etherscan_api_key: str | None = None, production=False, meta: str = "", uniswap_v2=True, uniswap_v3=True, mock_guard=False, ) -> Vault: """Deploy an Enzyme vault and make it secure. Deploys an Enzyme vault in a specific way we want to have it deployed. - Because we want multiple deployed smart contracts to be verified on Etherscan, this deployed uses a Forge-based toolchain and thus the script can be only run from the git checkout where submodules are included. - Set up default policies - Assign a generic adapter - Assign a USDC payment forwarder with terms of service sign up - Assign asset manager role and transfer ownership - Whitelist USDC and the other given assets - Whitelist Uniswap v2 and v3 spot routers .. note :: The GuardV0 ownership is **not** transferred to the owner at the end of the deployment. You need to do it manually after configuring the guard. :param deployment: Enzyme deployment we use. :param deployer: Web3.py deployer account we use. :param asset_manager: Give trading access to this hot wallet address. Set to the deployer address to ignore. :param terms_of_service: Terms of service contract we use. :param owner: Nominated new owner. Immediately transfer vault ownership from a deployer to a multisig owner. Multisig needs to confirm this by calling `claimOwnership`. Set to the deployer address to ignore. :param whitelisted_assets: Whitelist these assets on Uniswap v2 and v3 spot market. USDC is always whitelisted. :param denomination_asset: USDC token used as the vault denomination currency. :param etherscan_api_key: Needed to verify deployed contracts. :param production: Production flag set on `GuardedGenericAdapterDeployed` event. :param meta: Metadata for `GuardedGenericAdapterDeployed` event. :param uniswap_v2: Whiteliste Uniswap v2 trading :param uniswap_v3: Whiteliste Uniswap v3 trading :param mock_guard: Deploy unit test mock of the guard :return: Freshly deployed vault """ assert isinstance(deployer, HotWallet), f"Got {type(deployer)}" assert asset_manager.startswith("0x") assert owner.startswith("0x") assert CONTRACTS_ROOT.exists(), f"Cannot find contracts folder {CONTRACTS_ROOT.resolve()} - are you runnign from git checkout?" whitelisted_assets = whitelisted_assets or [] for asset in whitelisted_assets: assert isinstance(asset, TokenDetails) # Log EtherScan API key # Nothing bad can be done with this key, but good diagnostics is more important web3 = deployment.web3 deployed_at_block = web3.eth.block_number logger.info( "Deploying Enzyme vault. Enzyme fund deployer: %s, Terms of service: %s, USDC: %s, Etherscan API key: %s, block %d", deployment.contracts.fund_deployer.address, terms_of_service.address if terms_of_service is not None else "-", denomination_asset.address, etherscan_api_key, deployed_at_block, ) if not mock_guard: guard, tx_hash = deploy_contract_with_forge( web3, CONTRACTS_ROOT / "guard", "GuardV0.sol", f"GuardV0", deployer, etherscan_api_key=etherscan_api_key, ) logger.info("GuardV0 is %s deployed at %s", guard.address, tx_hash.hex()) assert guard.functions.getInternalVersion().call() == 1 else: # Unit testing path guard, tx_hash = deploy_contract_with_forge( web3, CONTRACTS_ROOT / "guard", "MockGuard.sol", f"MockGuard", deployer, etherscan_api_key=etherscan_api_key, ) logger.info("MockGuard is %s deployed at %s", guard.address, tx_hash.hex()) generic_adapter, tx_hash = deploy_contract_with_forge( web3, CONTRACTS_ROOT / "in-house", "GuardedGenericAdapter.sol", "GuardedGenericAdapter", deployer, [deployment.contracts.integration_manager.address, guard.address], etherscan_api_key=etherscan_api_key, ) logger.info("GuardedGenericAdapter is %s deployed at %s", generic_adapter.address, tx_hash.hex()) if deployment.contracts.cumulative_slippage_tolerance_policy is not None: policy_configuration = create_safe_default_policy_configuration_for_generic_adapter( deployment, generic_adapter, ) else: # Legacy + unit test policy_configuration = None comptroller, vault = deployment.create_new_vault( deployer.address, denomination_asset, policy_configuration=policy_configuration, fund_name=fund_name, fund_symbol=fund_symbol, ) assert comptroller.functions.getDenominationAsset().call() == denomination_asset.address assert vault.functions.getTrackedAssets().call() == [denomination_asset.address] # asset manager role is the trade executor if asset_manager != deployer.address: tx_hash = vault.functions.addAssetManagers([asset_manager]).transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) # Need to resync the nonce, because it was used outside HotWallet deployer.sync_nonce(web3) if terms_of_service is not None: payment_forwarder, tx_hash = deploy_contract_with_forge( web3, CONTRACTS_ROOT / "in-house", "TermedVaultUSDCPaymentForwarder.sol", "TermedVaultUSDCPaymentForwarder", deployer, [denomination_asset.address, comptroller.address, terms_of_service.address], etherscan_api_key=etherscan_api_key, ) logger.info("TermedVaultUSDCPaymentForwarder is %s deployed at %s", payment_forwarder.address, tx_hash.hex()) else: # Legacy + unit test path payment_forwarder, tx_hash = deploy_contract_with_forge( web3, CONTRACTS_ROOT / "in-house", "VaultUSDCPaymentForwarder.sol", "VaultUSDCPaymentForwarder", deployer, [denomination_asset.address, comptroller.address], etherscan_api_key=etherscan_api_key, ) logger.info("VaultUSDCPaymentForwarder is %s deployed at %s", payment_forwarder.address, tx_hash.hex()) if not mock_guard: # When swap is performed, the tokens will land on the integration contract # and this contract must be listed as the receiver. # Enzyme will then internally move tokens to its vault from here. tx_hash = guard.functions.allowReceiver(generic_adapter.address, "").transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) # Because Enzyme does not pass the asset manager address to through integration manager, # we set the vault address itself as asset manager for the guard tx_hash = guard.functions.allowSender(vault.address, "").transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) logger.info("GenericAdapter %s whitelisted as receiver", generic_adapter.address) else: # Production deployment foobar - add this warning message for now until figuring # out why allowReceiver() failed logger.warning("No receiver whitelisted") # Give generic adapter back reference to the vault assert vault.functions.getCreator().call() != ZERO_ADDRESS, f"Bad vault creator {vault.functions.getCreator().call()}" tx_hash = generic_adapter.functions.bindVault( vault.address, production, meta, ).transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) assert generic_adapter.functions.getIntegrationManager().call() == deployment.contracts.integration_manager.address assert comptroller.functions.getDenominationAsset().call() == denomination_asset.address assert vault.functions.getTrackedAssets().call() == [denomination_asset.address] if asset_manager != deployer.address: assert vault.functions.canManageAssets(asset_manager).call() if not mock_guard: assert guard.functions.isAllowedSender(vault.address).call() # vault = asset manager for the guard usdc_token = fetch_erc20_details(web3, denomination_asset.address) all_assets = [usdc_token] + whitelisted_assets for asset in all_assets: logger.info("Whitelisting %s", asset) tx_hash = guard.functions.whitelistToken(asset.address, f"Whitelisting {asset.symbol}").transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) # We cannot directly transfer the ownership to a multisig, # but we can set nominated ownership pending if owner != deployer.address: tx_hash = vault.functions.setNominatedOwner(owner).transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) logger.info("New vault owner nominated to be %s", owner) vault = Vault.fetch( web3, vault_address=vault.address, payment_forwarder=payment_forwarder.address, generic_adapter_address=generic_adapter.address, deployed_at_block=deployed_at_block, asset_manager=asset_manager, ) vault.deployer_hot_wallet = deployer assert vault.guard_contract.address == guard.address if not mock_guard: match web3.eth.chain_id: case 137: uniswap_v3_router = UNISWAP_V3_DEPLOYMENTS["polygon"]["router"] uniswap_v2_router = QUICKSWAP_DEPLOYMENTS["polygon"]["router"] case 1: uniswap_v2_router = UNISWAP_V2_DEPLOYMENTS["ethereum"]["router"] uniswap_v3_router = UNISWAP_V3_DEPLOYMENTS["ethereum"]["router"] case _: logger.info("Uniswap not supported for chain %d", web3.eth.chain_id) uniswap_v2_router = None uniswap_v3_router = None if uniswap_v2 and uniswap_v2_router: logger.info("Whitelisting Uniswap/Quickswap V2 router %s", uniswap_v2_router) tx_hash = vault.guard_contract.functions.whitelistUniswapV2Router(uniswap_v2_router, "").transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) if uniswap_v3 and uniswap_v3_router: logger.info("Whitelisting Uniswap V3 router %s", uniswap_v3_router) tx_hash = vault.guard_contract.functions.whitelistUniswapV3Router(uniswap_v3_router, "").transact({"from": deployer.address}) assert_transaction_success_with_explanation(web3, tx_hash) logger.info( "Deployed. Vault is %s, initial owner is %s, asset manager is %s", vault.vault.address, vault.get_owner(), asset_manager, ) return vault