Skip to content

Commit

Permalink
Enzyme vault deployment script (#190)
Browse files Browse the repository at this point in the history
- Add a tutorial script and Polygon data to deploy Enzyme vaults programmatically
  • Loading branch information
miohtama authored Jan 25, 2024
1 parent eba4a15 commit e7fb10c
Showing 7 changed files with 71 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
- Add 1delta price estimation helper `OneDeltaPriceHelper`
- Add `fetch-all-vaults.py` export all Enzyme vaults from the chain to a CSV file
- Add `deploy_generic_adapter_vault` for correctly configured policy and safe vault deployment
- Add Enzyme vault deployment tutorial
- Improve logging in `wait_and_broadcast_multiple_nodes` for post-mortem analysis
- `hash(SignedTransactionWithNonce)` now is `SignedTransactionWithNonce.hash`, Ethereum transaction hash
- Improve various utility functions
2 changes: 1 addition & 1 deletion eth_defi/chainlink/__init__.py
Original file line number Diff line number Diff line change
@@ -5,4 +5,4 @@
- Scanning Chainlink price feeds
See :ref:`tutorials` how to use.
"""
"""
23 changes: 22 additions & 1 deletion eth_defi/enzyme/deployment.py
Original file line number Diff line number Diff line change
@@ -47,6 +47,11 @@
"wmatic": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
"fund_value_calculator": "0xcdf038Dd3b66506d2e5378aee185b2f0084B7A33",
"deployed_at": 25_825_795, # When comptroller lib was deployed
"cumulative_slippage_tolerance_policy": "0x1332367C181F1157F751b160187DcAa219706bF2",
"allowed_adapters_policy": "0x4218783aE10BD1841E6664cF048ac295D8d27a4a",
"only_remove_dust_external_position_policy": "0xC0f49507c125a000e02ab58C22bE9764e2ABAB99",
"only_untrack_dust_or_priceless_assets_policy": "0x9F856372F7Bd844dac0254c7859B117259b5c9D2",
"allowed_external_position_types_policy": "0x5A739da3099fd4fC954BD764099Fc000Da76D8e7",
}

#: Enzyme deployment details for Ehereum
@@ -141,6 +146,13 @@ def get_deployed_contract(self, contract_name: str, address: HexAddress) -> Cont
contract = get_deployed_contract(self.web3, f"enzyme/{contract_name}.json", address)
return contract

def get_optionally_deployed_contract(self, contract_name: str, address: HexAddress | None) -> Contract | None:
"""Helper access for IVault and IComptroller"""
if address is None:
return None
contract = get_deployed_contract(self.web3, f"enzyme/{contract_name}.json", address)
return contract

def get_all_addresses(self) -> Dict[str, str]:
"""Return all labeled addresses as a dict.
@@ -579,11 +591,20 @@ def fetch_deployment(web3: Web3, contract_addresses: dict) -> "EnzymeDeployment"
contracts.integration_manager = contracts.get_deployed_contract("IntegrationManager", contracts.comptroller_lib.functions.getIntegrationManager().call())
contracts.value_interpreter = contracts.get_deployed_contract("ValueInterpreter", contracts.comptroller_lib.functions.getValueInterpreter().call())

# Load policy contracts if we know their addresses
contracts.cumulative_slippage_tolerance_policy = contracts.get_optionally_deployed_contract("CumulativeSlippageTolerancePolicy", contract_addresses.get("cumulative_slippage_tolerance_policy"))
contracts.allowed_adapters_policy = contracts.get_optionally_deployed_contract("AllowedAdaptersPolicy", contract_addresses.get("allowed_adapters_policy"))
contracts.only_remove_dust_external_position_policy = contracts.get_optionally_deployed_contract("OnlyRemoveDustExternalPositionPolicy", contract_addresses.get("only_remove_dust_external_position_policy"))
contracts.only_untrack_dust_or_priceless_assets_policy = contracts.get_optionally_deployed_contract("OnlyUntrackDustOrPricelessAssetsPolicy", contract_addresses.get("only_untrack_dust_or_priceless_assets_policy"))
contracts.allowed_external_position_types_policy = contracts.get_optionally_deployed_contract("AllowedExternalPositionTypesPolicy", contract_addresses.get("allowed_external_position_types_policy"))

mln = get_deployed_contract(web3, "ERC20MockDecimals.json", contracts.comptroller_lib.functions.getMlnToken().call())
weth = get_deployed_contract(web3, "ERC20MockDecimals.json", contracts.comptroller_lib.functions.getWethToken().call())

if "usdc" in contract_addresses:
usdc = get_deployed_contract(web3, "ERC20MockDecimals.json", contract_addresses["udsc"])
usdc = get_deployed_contract(web3, "ERC20MockDecimals.json", contract_addresses["usdc"])
else:
usdc = None

return EnzymeDeployment(
web3,
1 change: 1 addition & 0 deletions eth_defi/enzyme/policy.py
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ class AddressListUpdateType(enum.Enum):
Taken from Enzyme's JS core.
"""

None_ = 0
AddOnly = 1
RemoveOnly = 2
39 changes: 34 additions & 5 deletions eth_defi/enzyme/vault.py
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
from eth_defi.event_reader.filter import Filter
from eth_defi.event_reader.reader import Web3EventReader
from eth_defi.token import TokenDetails, fetch_erc20_details
from eth_defi.uniswap_v2.utils import ZERO_ADDRESS


@dataclass
@@ -107,6 +108,18 @@ class Vault:
#:
deployed_at_block: int | None = None

#: If this vault has a dedicated asset manager address set for it.
#:
#: Vaults can have multiple asset managers, but it is rare.
#:
asset_manager: str | None = None

#: If this vault was set to be transferred to a owner multisig after the deployment.
#:
#: The owner needs to confirm the transfer.
#:
nominated_owner: str | None = None

def __repr__(self) -> str:
return f"<Vault vault={self.vault.address} adapter={self.generic_adapter and self.generic_adapter.address} payment_forwader={self.payment_forwarder and self.payment_forwarder.address}>"

@@ -120,10 +133,12 @@ def get_deployment_info(self) -> dict:
"""
return {
"VAULT_ADDRESS": self.vault.address,
"VAULT_ADAPTER_ADDRESS": self.generic_adapter.address,
"VAULT_PAYMENT_FORWARDER_ADDRESS": self.payment_forwarder.address,
"VAULT_GUARD_ADDRESS": self.guard_contract.address,
"VAULT_DEPLOYMENT_BLOCK_NUMBER": self.deployed_at_block,
"VAULT_ADAPTER_ADDRESS": self.generic_adapter.address if self.generic_adapter else "",
"VAULT_PAYMENT_FORWARDER_ADDRESS": self.payment_forwarder.address if self.payment_forwarder else "",
"VAULT_GUARD_ADDRESS": self.guard_contract.address if self.guard_contract else "",
"VAULT_DEPLOYMENT_BLOCK_NUMBER": self.deployed_at_block or "",
"VAULT_ASSET_MANAGER_ADDRESS": self.asset_manager or "",
"VAULT_NOMINATED_OWNER_ADDRESS": self.nominated_ower or "",
}

@property
@@ -324,8 +339,16 @@ def fetch(
generic_adapter_address: str | HexAddress | None = None,
payment_forwarder: str | HexAddress | None = None,
deployed_at_block: int | None = None,
asset_manager: HexAddress | None = None,
) -> "Vault":
"""Fetch Enzyme vault and deployment information based only on the vault address."""
"""Fetch Enzyme vault and deployment information based only on the vault address.
Because vault does not have a way to cross-reference its contracts,
we are now manually passing around a bunch of contracts and addresses.
:return:
Enzyme vault instance with all the information populated in
"""

contract_name = "VaultLib"
vault_contract = get_deployed_contract(web3, f"enzyme/{contract_name}.json", vault_address)
@@ -365,6 +388,10 @@ def fetch(
except:
pass

nominated_owner = vault_contract.functions.getNominatedOwner().call()
if nominated_owner == ZERO_ADDRESS:
nominated_owner = None

return Vault(
vault_contract,
comptroller_contract,
@@ -373,4 +400,6 @@ def fetch(
payment_forwarder_contract,
guard_contract,
deployed_at_block=deployed_at_block,
nominated_owner=nominated_owner,
asset_manager=asset_manager, # We cannot read asset manager back from the vault because it's just EVM hash map
)
8 changes: 1 addition & 7 deletions scripts/enzyme/cumulative-slippage-test.py
Original file line number Diff line number Diff line change
@@ -35,11 +35,7 @@
# )
# print(f"Generic adapter is {generic_adapter.address}")

generic_adapter = get_deployed_contract(
web3,
f"VaultSpecificGenericAdapter.json",
"0x8C35a027FE7986FA5736813869C0A2A7A991BEDd"
)
generic_adapter = get_deployed_contract(web3, f"VaultSpecificGenericAdapter.json", "0x8C35a027FE7986FA5736813869C0A2A7A991BEDd")
vault.generic_adapter = generic_adapter

wmatic = fetch_erc20_details(web3, "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270").contract
@@ -115,5 +111,3 @@
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

print("Receipt", receipt)


29 changes: 11 additions & 18 deletions scripts/enzyme/deploy-vault.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
"""Deploy a new Enzyme vault with a generic adapter.
- Deploys a tailored Enzyme vault - a different what you would be able eto deploy through Enzyme user interface
- Deploys a tailored Enzyme vault with custom policies and adapters.
This is a different what you would be able eto deploy through Enzyme user interface.
- The adapter is configured to use the generic adapter for trading from eth_defi package
- The adapter is configured to use the generic adapter for trading from eth_defi package,
- The custom deposit and terms of service contracts are bound to the vault.
- The deposit and terms of service contracts are present
"""

import csv
import sys
from functools import lru_cache
import logging
import os

import coloredlogs
from eth_account import Account

from eth_defi.abi import get_deployed_contract
from eth_defi.enzyme.deployment import POLYGON_DEPLOYMENT, ETHEREUM_DEPLOYMENT, EnzymeDeployment
from eth_defi.enzyme.generic_adapter_vault import deploy_generic_adapter_vault
from eth_defi.enzyme.price_feed import UnsupportedBaseAsset
from eth_defi.enzyme.vault import Vault
from eth_defi.event_reader.conversion import convert_uint256_bytes_to_address, convert_uint256_string_to_address, decode_data
from eth_defi.event_reader.filter import Filter
from eth_defi.event_reader.multithread import MultithreadEventReader
from eth_defi.event_reader.progress_update import PrintProgressUpdate
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.token import TokenDetails, fetch_erc20_details
from eth_defi.chainlink.token_price import get_native_token_price_with_chainlink, get_token_price_with_chainlink
from eth_defi.utils import setup_console_logging
from tradeexecutor.monkeypatch.web3 import construct_sign_and_send_raw_middleware

@@ -87,14 +78,18 @@ def main():
logger.info("Ownership will be transferred to %s", owner_address)
else:
logger.info("WARNING! Ownership will be retained at the deployer %d", deployer.address)
logger.info("Asset manager is %s", asset_manager_address)

if asset_manager_address != deployer.address:
logger.info("Asset manager is %s", asset_manager_address)
else:
logger.info("WARNING! No separate asset manager role set")

confirm = input("Ok [y/n]?")
if not confirm.lower().startswith("y"):
print("Aborted")
sys.exit(1)

logger.info("Deploying")
logger.info("Starting deployment")

vault = deploy_generic_adapter_vault(
enzyme,
@@ -109,5 +104,3 @@ def main():

if __name__ == "__main__":
main()


0 comments on commit e7fb10c

Please sign in to comment.