Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for updating Lagoon vault fees #274

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 81 additions & 32 deletions eth_defi/lagoon/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

Any Safe must be deployed as 1-of-1 deployer address multisig and multisig holders changed after the deployment.
"""

import logging
import os
import time
Expand All @@ -23,6 +24,7 @@
from eth_typing import HexAddress, BlockNumber
from hexbytes import HexBytes
from safe_eth.safe.safe import Safe
from safe_eth.safe.api.transaction_service_api.transaction_service_api import TransactionServiceApi

from web3 import Web3
from web3.contract import Contract
Expand All @@ -43,21 +45,34 @@
from eth_defi.uniswap_v3.deployment import UniswapV3Deployment
from eth_defi.vault.base import VaultSpec


logger = logging.getLogger(__name__)

# https://github.com/hopperlabsxyz/lagoon-v0
LAGOON_BEACONS = {
# Base
8453: "0xD69BC314bdaa329EB18F36E4897D96A3A48C3eeF",
}


# https://github.com/hopperlabsxyz/lagoon-v0
LAGOON_FEE_REGISTRIES = {
# Base
8453: "0x6dA4D1859bA1d02D095D2246142CdAd52233e27C",
}

DEFAULT_RATE_UPDATE_COOLDOWN = 86400

DEFAULT_MANAGEMENT_RATE = 200

DEFAULT_PERFORMANCE_RATE = 2000


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


@dataclass(slots=True)
class LagoonDeploymentParameters:
"""Capture core parameters needed to deploy a Lagoon vault"""

underlying: HexAddress
name: str
symbol: str
Expand Down Expand Up @@ -92,6 +107,7 @@ class LagoonAutomatedDeployment:

- Have the deployment report for the users for diagnostics
"""

chain_id: int
vault: LagoonVault
trading_strategy_module: Contract
Expand Down Expand Up @@ -130,7 +146,7 @@ def pformat(self) -> str:
fields = self.get_deployment_data()
# https://stackoverflow.com/a/17330263/315168
io = StringIO()
print("{:<30} {:30}".format('Key', 'Label'), file=io)
print("{:<30} {:30}".format("Key", "Label"), file=io)
for k, v in fields.items():
print("{:<30} {:<30}".format(k, v), file=io)

Expand All @@ -148,7 +164,7 @@ def deploy_lagoon(
etherscan_api_key: str = None,
use_forge=False,
beacon_proxy=True,
beacon_address = "0x652716FaD571f04D26a3c8fFd9E593F17123Ab20"
beacon_address="0x652716FaD571f04D26a3c8fFd9E593F17123Ab20",
) -> Contract:
"""Deploy a new Lagoon vault.

Expand Down Expand Up @@ -226,10 +242,7 @@ def deploy_lagoon(

init_struct = parameters.as_solidity_struct()

logger.info(
"Parameters are:\n%s",
pformat(init_struct)
)
logger.info("Parameters are:\n%s", pformat(init_struct))

# TODO: Beacon proxy deployment does not work

Expand All @@ -252,11 +265,13 @@ def deploy_lagoon(
False,
)

tx_params = vault.functions.initialize(init_struct).build_transaction({
"gas": 2_000_000,
"chainId": chain_id,
"nonce": web3.eth.get_transaction_count(deployer.address),
})
tx_params = vault.functions.initialize(init_struct).build_transaction(
{
"gas": 2_000_000,
"chainId": chain_id,
"nonce": web3.eth.get_transaction_count(deployer.address),
}
)
signed_tx = deployer.sign_transaction(tx_params)
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
assert_transaction_success_with_explanation(web3, tx_hash)
Expand Down Expand Up @@ -339,9 +354,7 @@ def deploy_safe_trading_strategy_module(

# Enable TradingStrategyModuleV0 as Safe module
# Multisig owners can enable the module
tx = safe.contract.functions.enableModule(module.address).build_transaction(
{"from": deployer.address, "gas": 0, "gasPrice": 0}
)
tx = safe.contract.functions.enableModule(module.address).build_transaction({"from": deployer.address, "gas": 0, "gasPrice": 0})
safe_tx = safe.build_multisig_tx(safe.address, 0, tx["data"])
safe_tx.sign(deployer._private_key.hex())
tx_hash, tx = safe_tx.execute(
Expand Down Expand Up @@ -449,7 +462,7 @@ def deploy_automated_lagoon_vault(
Deployer account must be manually removed from the Safe by new owners.
"""

assert len(safe_owners) >= 1, "Multisig owners emptty"
assert len(safe_owners) >= 1, "Multisig owners empty"

chain_id = web3.eth.chain_id

Expand Down Expand Up @@ -542,11 +555,13 @@ def _broadcast(bound_func: ContractFunction):

# 2. USDC.approve() for redemptions on Safe
underlying = fetch_erc20_details(web3, parameters.underlying, chain_id=chain_id)
tx_data = underlying.contract.functions.approve(vault_contract.address, 2**256-1).build_transaction({
"from": deployer.address,
"gas": 0,
"gasPrice": 0,
})
tx_data = underlying.contract.functions.approve(vault_contract.address, 2**256 - 1).build_transaction(
{
"from": deployer.address,
"gas": 0,
"gasPrice": 0,
}
)
safe_tx = safe.build_multisig_tx(underlying.address, 0, tx_data["data"])
safe_tx.sign(deployer_local_account._private_key.hex())
tx_hash, tx = safe_tx.execute(
Expand Down Expand Up @@ -581,17 +596,51 @@ def _broadcast(bound_func: ContractFunction):
)


# https://github.com/hopperlabsxyz/lagoon-v0
LAGOON_BEACONS = {
# Base
8453: "0xD69BC314bdaa329EB18F36E4897D96A3A48C3eeF",
}
def update_lagoon_vault_fees(
web3: Web3,
*,
deployer: LocalAccount | HotWallet,
vault_spec: VaultSpec,
management_rate: int,
performance_rate: int,
) -> None:
"""
Update the management and performance fees for the Lagoon vault.

NOTE: this function only proposes a tx to the Safe, the tx must be confirmed by the Safe owners.

# https://github.com/hopperlabsxyz/lagoon-v0
LAGOON_FEE_REGISTRIES = {
# Base
8453: "0x6dA4D1859bA1d02D095D2246142CdAd52233e27C",
}
:param deployer:
The deployer account

:param vault_spec:
The vault specification

:param management_rate:
The management fee in BPS

:param performance_rate:
The performance fee in BPS
"""
if isinstance(deployer, HotWallet):
# Production nonce hack
deployer_local_account = deployer.account
else:
deployer_local_account = deployer

vault = LagoonVault(web3, vault_spec)
safe = vault.safe

tx_data = vault.vault_contract.functions.updateRates((management_rate, performance_rate)).build_transaction(
{
"from": deployer_local_account.address,
"gas": 0,
"gasPrice": 0,
}
)

safe_tx = safe.build_multisig_tx(vault.vault_address, 0, tx_data["data"])
safe_tx.sign(deployer_local_account._private_key.hex())

# setup transaction service API and propose the tx to Safe
api = TransactionServiceApi.from_ethereum_client(safe.ethereum_client)
api.post_transaction(safe_tx)
20 changes: 1 addition & 19 deletions eth_defi/lagoon/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,6 @@ def name(self) -> str:
def symbol(self) -> str:
return self.share_token.symbol

@cached_property
def vault_contract(self) -> Contract:
"""Get vault deployment."""
return get_deployed_contract(
self.web3,
"lagoon/Vault.json",
self.spec.vault_address,
)

@cached_property
def vault_contract(self) -> Contract:
"""Underlying Vault smart contract."""
Expand Down Expand Up @@ -206,7 +197,7 @@ def fetch_info(self) -> LagoonVaultInfo:
See :py:class:`LagoonVaultInfo`
"""
vault_info = self.fetch_vault_info()
safe = self.fetch_safe(vault_info['safe'])
safe = self.fetch_safe(vault_info["safe"])
safe_info_dict = asdict(safe.retrieve_all_info())
del safe_info_dict["address"] # Key conflict
return vault_info | safe_info_dict
Expand Down Expand Up @@ -235,7 +226,6 @@ def fetch_total_assets(self, block_identifier: BlockIdentifier) -> Decimal:
raw_amount = self.vault_contract.functions.totalAssets().call(block_identifier=block_identifier)
return self.underlying_token.convert_to_decimals(raw_amount)


def fetch_total_supply(self, block_identifier: BlockIdentifier) -> Decimal:
"""What is the current outstanding shares.

Expand Down Expand Up @@ -634,11 +624,3 @@ def calculate_underlying_neeeded_for_redemptions(self, block_identifier: BlockId
shares_pending = self.fetch_pending_redemption(block_identifier)
share_price = self.vault.fetch_share_price(block_identifier)
return shares_pending * share_price








54 changes: 54 additions & 0 deletions scripts/lagoon/update-lagoon-fees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""An example script to update a Lagoon vault's fees.

To run:

.. code-block:: shell

export PRIVATE_KEY=...
export JSON_RPC_BASE=...
export VAULT_ADDRESS=...
export MANAGEMENT_RATE=...
export PERFORMANCE_RATE=...
python scripts/lagoon/update-lagoon-fees.py
"""

import logging
import os
import sys

from eth_defi.hotwallet import HotWallet
from eth_defi.lagoon.vault import VaultSpec
from eth_defi.lagoon.deployment import update_lagoon_vault_fees
from eth_defi.provider.anvil import fork_network_anvil
from eth_defi.provider.multi_provider import create_multi_provider_web3


logging.basicConfig(level=logging.INFO, stream=sys.stdout)


def main():
PRIVATE_KEY = os.environ["PRIVATE_KEY"]
JSON_RPC_BASE = os.environ["JSON_RPC_BASE"]
VAULT_ADDRESS = os.environ["VAULT_ADDRESS"]

MANAGEMENT_RATE = int(os.environ.get("MANAGEMENT_RATE", 0))
PERFORMANCE_RATE = int(os.environ.get("PERFORMANCE_RATE", 0))

deployer_wallet = HotWallet.from_private_key(PRIVATE_KEY)

web3 = create_multi_provider_web3(JSON_RPC_BASE)
chain_id = web3.eth.chain_id

update_lagoon_vault_fees(
web3=web3,
deployer=deployer_wallet,
vault_spec=VaultSpec(chain_id, VAULT_ADDRESS),
management_rate=MANAGEMENT_RATE,
performance_rate=PERFORMANCE_RATE,
)

print(f"Lagoon vault fees proposed to: management {MANAGEMENT_RATE} & performance {PERFORMANCE_RATE}, please confirm the tx on Safe")


if __name__ == "__main__":
main()
Loading