From cb81d8a32e82111cfdaf0583a3aec2e9d31fb3c5 Mon Sep 17 00:00:00 2001 From: paulvaden Date: Wed, 23 Oct 2024 10:44:38 -0400 Subject: [PATCH 01/19] Add derive exchange balances --- .env.example | 1 + constants/chains.py | 1 + constants/integration_ids.py | 1 + constants/lyra.py | 32 +++++--- integrations/lyra_susde_bull.py | 40 ++++++---- utils/lyra.py | 129 ++++++++++++++++++++++++++++++-- utils/web3_utils.py | 5 ++ 7 files changed, 179 insertions(+), 30 deletions(-) diff --git a/.env.example b/.env.example index 1eba3de..c78641f 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,4 @@ ARBITRUM_NODE_URL='https://arb1.arbitrum.io/rpc' SCROLL_NODE_URL='https://rpc.scroll.io' MODE_NODE_URL='https://mainnet.mode.network' FRAXTAL_NODE_URL='https://rpc.frax.com' +LYRA_NODE_URL='https://rpc.derive.xyz' \ No newline at end of file diff --git a/constants/chains.py b/constants/chains.py index 4861249..1c6a6cf 100644 --- a/constants/chains.py +++ b/constants/chains.py @@ -9,3 +9,4 @@ class Chain(Enum): BLAST = "Blast" SCROLL = "Scroll" MODE = "Mode" + Lyra = "Lyra" diff --git a/constants/integration_ids.py b/constants/integration_ids.py index e29ed08..78be68a 100644 --- a/constants/integration_ids.py +++ b/constants/integration_ids.py @@ -75,6 +75,7 @@ class IntegrationID(Enum): # Lyra LYRA_SUSDE_BULL_MAINNET = ("lyra_susde_bull_mainnet", "Lyra sUSDe Bull Vault Mainnet", Token.SUSDE) LYRA_SUSDE_BULL_ARBITRUM = ("lyra_susde_bull_arbitrum", "Lyra sUSDe Bull Vault Arbitrum", Token.SUSDE) + LYRA_SUSDE_EXCHANGE_DEPOSIT= ("lyra_susde_exchange_deposit", "Lyra sUSDe Exchange Deposits", Token.SUSDE) # Velodrome VELODROME_MODE_USDE = ('velodrome_mode_usde', 'Velodrome Mode USDe', Token.USDE) VELODROME_MODE_SUSDE = ('velodrome_mode_susde', 'Velodrome Mode sUSDe', Token.SUSDE) diff --git a/constants/lyra.py b/constants/lyra.py index a06e70a..8d298e7 100644 --- a/constants/lyra.py +++ b/constants/lyra.py @@ -5,48 +5,62 @@ from web3 import Web3 from constants.chains import Chain from constants.integration_ids import IntegrationID +from enum import Enum with open("abi/ERC20_abi.json") as f: erc20_abi = json.load(f) +class DetailType(Enum): + Vault = "Vault" + Exchange = "Exchange" + class LyraVaultDetails(TypedDict): + detail_type: DetailType start: int chain: str - integration_token: Contract - bridge: Contract - vault_token: Contract page_size: int + integration_token: Contract | None + bridge: Contract| None + vault_token: Contract| None # NOTE: does not handle cross-chain transfers of vault tokens LYRA_CONTRACTS_AND_START_BY_TOKEN: Dict[IntegrationID, LyraVaultDetails] = { IntegrationID.LYRA_SUSDE_BULL_MAINNET: LyraVaultDetails( + detail_type=DetailType.Vault, start=20211445, chain=Chain.ETHEREUM, - integration_token=W3_BY_CHAIN[Chain.ETHEREUM].eth.contract( + integration_token=W3_BY_CHAIN[Chain.ETHEREUM]["w3"].eth.contract( address=Web3.to_checksum_address("0x9d39a5de30e57443bff2a8307a4256c8797a3497"), abi=erc20_abi ), # sUSDe - bridge=W3_BY_CHAIN[Chain.ETHEREUM].eth.contract( + bridge=W3_BY_CHAIN[Chain.ETHEREUM]["w3"].eth.contract( address=Web3.to_checksum_address("0xE3E96892D30E0ee1a8131BAf87c891201F7137bf"), abi=erc20_abi ), - vault_token=W3_BY_CHAIN[Chain.ETHEREUM].eth.contract( + vault_token=W3_BY_CHAIN[Chain.ETHEREUM]["w3"].eth.contract( address=Web3.to_checksum_address("0x1d080C689B930f9dEa69CB3B4Bc6b8c213DFC2ad"), abi=erc20_abi ), page_size=5000, ), IntegrationID.LYRA_SUSDE_BULL_ARBITRUM: LyraVaultDetails( + detail_type=DetailType.Vault, start=227626020, chain=Chain.ARBITRUM, - integration_token=W3_BY_CHAIN[Chain.ARBITRUM].eth.contract( + integration_token=W3_BY_CHAIN[Chain.ARBITRUM]["w3"].eth.contract( address=Web3.to_checksum_address("0x211cc4dd073734da055fbf44a2b4667d5e5fe5d2"), abi=erc20_abi ), # sUSDe - bridge=W3_BY_CHAIN[Chain.ARBITRUM].eth.contract( + bridge=W3_BY_CHAIN[Chain.ARBITRUM]["w3"].eth.contract( address=Web3.to_checksum_address("0x3c143EA5eBaB50ad6D2B2d14FA719234d1d38F1b"), abi=erc20_abi ), - vault_token=W3_BY_CHAIN[Chain.ARBITRUM].eth.contract( + vault_token=W3_BY_CHAIN[Chain.ARBITRUM]["w3"].eth.contract( address=Web3.to_checksum_address("0x81494d722DDceDbA31ac40F28daFa66b207f232B"), abi=erc20_abi ), page_size=20000, ), + IntegrationID.LYRA_SUSDE_EXCHANGE_DEPOSIT: LyraVaultDetails( + detail_type=DetailType.Exchange, + start=11481048, + chain=Chain.Lyra, + page_size=20000, + ) } diff --git a/integrations/lyra_susde_bull.py b/integrations/lyra_susde_bull.py index 1fc3cc3..1b994aa 100644 --- a/integrations/lyra_susde_bull.py +++ b/integrations/lyra_susde_bull.py @@ -3,8 +3,8 @@ from constants.integration_ids import IntegrationID from models.integration import Integration -from utils.lyra import get_vault_users, get_effective_balance -from constants.lyra import LYRA_CONTRACTS_AND_START_BY_TOKEN, LyraVaultDetails +from utils.lyra import get_vault_users, get_effective_balance, get_exchange_users, get_exchange_balance +from constants.lyra import LYRA_CONTRACTS_AND_START_BY_TOKEN, LyraVaultDetails, DetailType class LyraIntegration(Integration): @@ -25,29 +25,37 @@ def __init__(self, integration_id: IntegrationID): ) def get_balance(self, user: str, block: int) -> float: - return get_effective_balance( - user, - block, - self.vault_data["integration_token"], - self.vault_data["bridge"], - self.vault_data["vault_token"], - ) + if self.vault_data["detail_type"] == DetailType.Vault: + return get_effective_balance( + user, + block, + self.vault_data["integration_token"], + self.vault_data["bridge"], + self.vault_data["vault_token"], + W3_BY_CHAIN[example_integration.chain]["w3"].eth.get_block(block)['timestamp'] + ) + + else: + return get_exchange_balance(user, block) def get_participants(self) -> list: logging.info(f"[{self.integration_id.get_description()}] Getting participants...") - self.participants = get_vault_users( - self.start_block, - self.vault_data["page_size"], - self.vault_data["vault_token"], - self.chain, - ) + if self.vault_data["detail_type"] == DetailType.Vault: + self.participants = get_vault_users( + self.start_block, + self.vault_data["page_size"], + self.vault_data["vault_token"], + self.chain, + ) + else: + self.participants = get_exchange_users() return self.participants if __name__ == "__main__": example_integration = LyraIntegration(IntegrationID.LYRA_SUSDE_BULL_MAINNET) - current_block = W3_BY_CHAIN[example_integration.chain].eth.get_block_number() + current_block = W3_BY_CHAIN[example_integration.chain]["w3"].eth.get_block_number() print("Found Lyra Participants:") print(example_integration.get_participants()) diff --git a/utils/lyra.py b/utils/lyra.py index 70e0884..dd448ec 100644 --- a/utils/lyra.py +++ b/utils/lyra.py @@ -2,24 +2,143 @@ W3_BY_CHAIN, fetch_events_logs_with_retry, ) +import os +import requests from web3.contract import Contract from utils.web3_utils import call_with_retry from constants.chains import Chain +from web3 import Web3 +url = 'https://app.sentio.xyz/api/v1/graphql/derive/v2_subgraph' +SUSDE_VAULT_ADDRESS = "0x0b4eD379da8eF4FCF06F697c5782CA7b4c3E505E" -def get_effective_balance(user: str, block: int, integration_token: Contract, bridge: Contract, vault_token: Contract): +user_balance_query = ''' +{ + accounts(where: {or: [{id: "{user}"}, {owner: "{user}"}]}, block: {number: {block}}) { + id + owner + subaccounts{ + subaccountId + balances(where:{asset: "0x375804cdcf0d534fdd2657584a7c4ff5ab14a2bb000000000000000000000000"}){ + balance + } + } + depositedSubaccounts{ + subaccountId + balances(where:{asset: "0x375804cdcf0d534fdd2657584a7c4ff5ab14a2bb000000000000000000000000"}){ + balance + } + } + } +} +''' + + +def get_exchange_balance(user: str, block: int) -> int: + user = user.lower() + # Replace the user and block placeholders in the query + user_balance_query_filled = user_balance_query.replace("{user}", user).replace("{block}", str(block)) + + headers = { + 'Content-Type': 'application/json', + 'api-key': os.getenv("DERIVE_SUBGRAPHAPI_KEY") + } + print(user_balance_query_filled) + + # Send the POST request + response = requests.post(url, json={'query': user_balance_query_filled}, headers=headers) + + total_balance = 0 + if response.status_code == 200: + response_json = response.json() + accounts = response_json['data']['accounts'] + for account in accounts: + for subaccount in account['subaccounts']: + for balance in subaccount['balances']: + total_balance += float(balance['balance']) + for subaccount in account['depositedSubaccounts']: + for balance in subaccount['balances']: + total_balance += float(balance['balance']) + else: + print(f"Query failed with status code {response.status_code}: {response.text}") + return total_balance + +def get_effective_balance(user: str, block: int, integration_token: Contract, bridge: Contract, vault_token: Contract, timestamp): """ Since vault tokens can be transferred, calculates the portion of totalSupply() of the vault held by user. User's effective ethena integration token balance = (vault.balanceOf(user) / vault.totalSupply()) * total bridge balance """ - total_bridge_balance = call_with_retry(integration_token.functions.balanceOf(bridge.address), block) - total_vault_token_balance = call_with_retry(vault_token.functions.totalSupply(), block) user_vault_token_balance = call_with_retry(vault_token.functions.balanceOf(user), block) - return (user_vault_token_balance / total_vault_token_balance) * total_bridge_balance + headers = { + 'Content-Type': 'application/json' + } + + # Send the POST request + response = requests.post("https://api.lyra.finance/public/get_vault_share", json={'from_timestamp_sec': 0, 'to_timestamp_sec': timestamp, 'vault_name': 'sUSDePrincipal Protected Bull Call Spread', 'page_size': 1}, headers=headers) + try: + response_json = response.json() + vault_price = float(response_json['result']['vault_shares'][0]["base_value"]) + except: + print("Failed to get vault price, using default") + vault_price = float(1) + + return user_vault_token_balance * vault_price /1e18 + + +all_users_query = ''' +{ + subAccountBalances(where: {asset: "0x375804cdcf0d534fdd2657584a7c4ff5ab14a2bb000000000000000000000000"}) { + subaccount { + id + owner { + id + owner + } + matchingOwner { + id + owner + } + } + } +} +''' + +def get_exchange_users() -> set: + headers = { + 'Content-Type': 'application/json', + 'api-key': os.getenv("DERIVE_SUBGRAPHAPI_KEY") + } + + response = requests.post(url, json={'query': all_users_query}, headers=headers) + + users = set() + if response.status_code == 200: + response_json = response.json() + balances = response_json['data']['subAccountBalances'] + for balance in balances: + if balance['subaccount']['matchingOwner'] is not None: + if balance['subaccount']['matchingOwner']['owner'] is not None: + users.add(Web3.to_checksum_address(balance['subaccount']['matchingOwner']['owner'])) + else: + users.add(Web3.to_checksum_address(balance['subaccount']['matchingOwner']['id'])) + else: + if balance['subaccount']['owner']['owner'] is not None: + users.add(Web3.to_checksum_address(balance['subaccount']['owner']['owner'])) + else: + users.add(Web3.to_checksum_address(balance['subaccount']['owner']['id'])) + else: + print(f"Query failed with status code {response.status_code}: {response.text}") + + try: + users.remove(SUSDE_VAULT_ADDRESS) # Ignore the sUSDe vault which is accounted for separately + except: + pass + + return users def get_vault_users(start_block: int, page_size: int, vault_token: Contract, chain: Chain): """ @@ -29,7 +148,7 @@ def get_vault_users(start_block: int, page_size: int, vault_token: Contract, cha """ all_users = set() - target_block = W3_BY_CHAIN[chain].eth.get_block_number() + target_block = W3_BY_CHAIN[chain]["w3"].eth.get_block_number() while start_block < target_block: to_block = min(start_block + page_size, target_block) diff --git a/utils/web3_utils.py b/utils/web3_utils.py index 019049a..f7cc40f 100644 --- a/utils/web3_utils.py +++ b/utils/web3_utils.py @@ -27,6 +27,8 @@ w3_mode = Web3(Web3.HTTPProvider(MODE_NODE_URL)) FRAXTAL_NODE_URL = os.getenv("FRAXTAL_NODE_URL") w3_fraxtal = Web3(Web3.HTTPProvider(FRAXTAL_NODE_URL)) +LYRA_NODE_URL = os.getenv("LYRA_NODE_URL") +w3_lyra = Web3(Web3.HTTPProvider(LYRA_NODE_URL)) W3_BY_CHAIN = { Chain.ETHEREUM: { @@ -50,6 +52,9 @@ Chain.FRAXTAL: { "w3": w3_fraxtal, }, + Chain.Lyra: { + "w3": w3_lyra, + }, } From 7d28c1c6c88263ec99b3da7d41d1a8a79ff7414b Mon Sep 17 00:00:00 2001 From: paulvaden Date: Wed, 23 Oct 2024 13:42:15 -0400 Subject: [PATCH 02/19] naming fixes --- .env.example | 4 +++- utils/lyra.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index c78641f..dcab450 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,6 @@ ARBITRUM_NODE_URL='https://arb1.arbitrum.io/rpc' SCROLL_NODE_URL='https://rpc.scroll.io' MODE_NODE_URL='https://mainnet.mode.network' FRAXTAL_NODE_URL='https://rpc.frax.com' -LYRA_NODE_URL='https://rpc.derive.xyz' \ No newline at end of file +LYRA_NODE_URL='https://rpc.derive.xyz' + +DERIVE_SUBGRAPH_API_KEY=' \ No newline at end of file diff --git a/utils/lyra.py b/utils/lyra.py index dd448ec..851a915 100644 --- a/utils/lyra.py +++ b/utils/lyra.py @@ -42,7 +42,7 @@ def get_exchange_balance(user: str, block: int) -> int: headers = { 'Content-Type': 'application/json', - 'api-key': os.getenv("DERIVE_SUBGRAPHAPI_KEY") + 'api-key': os.getenv("DERIVE_SUBGRAPH_API_KEY") } print(user_balance_query_filled) @@ -110,7 +110,7 @@ def get_effective_balance(user: str, block: int, integration_token: Contract, br def get_exchange_users() -> set: headers = { 'Content-Type': 'application/json', - 'api-key': os.getenv("DERIVE_SUBGRAPHAPI_KEY") + 'api-key': os.getenv("DERIVE_SUBGRAPH_API_KEY") } response = requests.post(url, json={'query': all_users_query}, headers=headers) From a5dcf431d6713c8e6b2e8f83b910175cbc600013 Mon Sep 17 00:00:00 2001 From: paulvaden Date: Wed, 23 Oct 2024 13:42:51 -0400 Subject: [PATCH 03/19] typo --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index dcab450..67d6f77 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,4 @@ MODE_NODE_URL='https://mainnet.mode.network' FRAXTAL_NODE_URL='https://rpc.frax.com' LYRA_NODE_URL='https://rpc.derive.xyz' -DERIVE_SUBGRAPH_API_KEY=' \ No newline at end of file +DERIVE_SUBGRAPH_API_KEY='' \ No newline at end of file From 77e9ef628ae4e428a2dd3c2c1a0bc63b3daed518 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 26 Sep 2024 16:57:16 -0400 Subject: [PATCH 04/19] initial commit --- abi/IHyperdriveMorpho.json | 1737 ++++++++++++++++++++++++++++++++++ abi/IMorpho.json | 630 ++++++++++++ abi/hyperdrive_registry.json | 555 +++++++++++ constants/hyperdrive.py | 23 + test_hyperdrive.py | 50 + utils/hyperdrive.py | 267 ++++++ 6 files changed, 3262 insertions(+) create mode 100644 abi/IHyperdriveMorpho.json create mode 100644 abi/IMorpho.json create mode 100644 abi/hyperdrive_registry.json create mode 100644 constants/hyperdrive.py create mode 100644 test_hyperdrive.py create mode 100644 utils/hyperdrive.py diff --git a/abi/IHyperdriveMorpho.json b/abi/IHyperdriveMorpho.json new file mode 100644 index 0000000..2ef9254 --- /dev/null +++ b/abi/IHyperdriveMorpho.json @@ -0,0 +1,1737 @@ +[ + { + "type": "function", + "name": "PERMIT_TYPEHASH", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addLiquidity", + "inputs": [ + { + "name": "_contribution", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_minLpSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "_minApr", "type": "uint256", "internalType": "uint256" }, + { "name": "_maxApr", "type": "uint256", "internalType": "uint256" }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "lpShares", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "adminController", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { "name": "tokenId", "type": "uint256", "internalType": "uint256" }, + { "name": "owner", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "baseToken", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "batchTransferFrom", + "inputs": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "ids", "type": "uint256[]", "internalType": "uint256[]" }, + { "name": "values", "type": "uint256[]", "internalType": "uint256[]" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "checkpoint", + "inputs": [ + { + "name": "_checkpointTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_maxIterations", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "closeLong", + "inputs": [ + { + "name": "_maturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "_bondAmount", "type": "uint256", "internalType": "uint256" }, + { "name": "_minOutput", "type": "uint256", "internalType": "uint256" }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "proceeds", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "closeShort", + "inputs": [ + { + "name": "_maturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "_bondAmount", "type": "uint256", "internalType": "uint256" }, + { "name": "_minOutput", "type": "uint256", "internalType": "uint256" }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "proceeds", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "collateralToken", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "collectGovernanceFee", + "inputs": [ + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "proceeds", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "convertToBase", + "inputs": [ + { "name": "_shareAmount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "convertToShares", + "inputs": [ + { "name": "_baseAmount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCheckpoint", + "inputs": [ + { + "name": "_checkpointTime", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IHyperdrive.Checkpoint", + "components": [ + { + "name": "weightedSpotPrice", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "lastWeightedSpotPriceUpdateTime", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "vaultSharePrice", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCheckpointExposure", + "inputs": [ + { + "name": "_checkpointTime", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [{ "name": "", "type": "int256", "internalType": "int256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getMarketState", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IHyperdrive.MarketState", + "components": [ + { + "name": "shareReserves", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "bondReserves", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "longExposure", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "longsOutstanding", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "shareAdjustment", + "type": "int128", + "internalType": "int128" + }, + { + "name": "shortsOutstanding", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "longAverageMaturityTime", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "shortAverageMaturityTime", + "type": "uint128", + "internalType": "uint128" + }, + { "name": "isInitialized", "type": "bool", "internalType": "bool" }, + { "name": "isPaused", "type": "bool", "internalType": "bool" }, + { + "name": "zombieBaseProceeds", + "type": "uint112", + "internalType": "uint112" + }, + { + "name": "zombieShareReserves", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolConfig", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IHyperdrive.PoolConfig", + "components": [ + { + "name": "baseToken", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "vaultSharesToken", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "linkerFactory", + "type": "address", + "internalType": "address" + }, + { + "name": "linkerCodeHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "initialVaultSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumShareReserves", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "minimumTransactionAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "circuitBreakerDelta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "positionDuration", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "checkpointDuration", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "timeStretch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "governance", + "type": "address", + "internalType": "address" + }, + { + "name": "feeCollector", + "type": "address", + "internalType": "address" + }, + { + "name": "sweepCollector", + "type": "address", + "internalType": "address" + }, + { + "name": "checkpointRewarder", + "type": "address", + "internalType": "address" + }, + { + "name": "fees", + "type": "tuple", + "internalType": "struct IHyperdrive.Fees", + "components": [ + { + "name": "curve", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "flat", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "governanceLP", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "governanceZombie", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getPoolInfo", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IHyperdrive.PoolInfo", + "components": [ + { + "name": "shareReserves", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "shareAdjustment", + "type": "int256", + "internalType": "int256" + }, + { + "name": "zombieBaseProceeds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "zombieShareReserves", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "bondReserves", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "lpTotalSupply", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "longsOutstanding", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "longAverageMaturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "shortsOutstanding", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "shortAverageMaturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "withdrawalSharesReadyToWithdraw", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "withdrawalSharesProceeds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "lpSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "longExposure", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getUncollectedGovernanceFees", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getWithdrawPool", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IHyperdrive.WithdrawPool", + "components": [ + { + "name": "readyToWithdraw", + "type": "uint128", + "internalType": "uint128" + }, + { "name": "proceeds", "type": "uint128", "internalType": "uint128" } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "id", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "Id" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_contribution", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "_apr", "type": "uint256", "internalType": "uint256" }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "lpShares", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "irm", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isApprovedForAll", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isPauser", + "inputs": [ + { "name": "_account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "kind", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "lltv", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "load", + "inputs": [ + { "name": "_slots", "type": "uint256[]", "internalType": "uint256[]" } + ], + "outputs": [ + { "name": "", "type": "bytes32[]", "internalType": "bytes32[]" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "name", + "inputs": [ + { "name": "tokenId", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "openLong", + "inputs": [ + { "name": "_amount", "type": "uint256", "internalType": "uint256" }, + { "name": "_minOutput", "type": "uint256", "internalType": "uint256" }, + { + "name": "_minVaultSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { + "name": "maturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "bondProceeds", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "openShort", + "inputs": [ + { "name": "_bondAmount", "type": "uint256", "internalType": "uint256" }, + { "name": "_maxDeposit", "type": "uint256", "internalType": "uint256" }, + { + "name": "_minVaultSharePrice", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { + "name": "maturityTime", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "deposit", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "oracle", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pause", + "inputs": [{ "name": "_status", "type": "bool", "internalType": "bool" }], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "perTokenApprovals", + "inputs": [ + { "name": "tokenId", "type": "uint256", "internalType": "uint256" }, + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitForAll", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "_approved", "type": "bool", "internalType": "bool" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "redeemWithdrawalShares", + "inputs": [ + { + "name": "_withdrawalShares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_minOutputPerShare", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "proceeds", "type": "uint256", "internalType": "uint256" }, + { + "name": "withdrawalSharesRedeemed", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeLiquidity", + "inputs": [ + { "name": "_lpShares", "type": "uint256", "internalType": "uint256" }, + { + "name": "_minOutputPerShare", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_options", + "type": "tuple", + "internalType": "struct IHyperdrive.Options", + "components": [ + { + "name": "destination", + "type": "address", + "internalType": "address" + }, + { "name": "asBase", "type": "bool", "internalType": "bool" }, + { "name": "extraData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "outputs": [ + { "name": "proceeds", "type": "uint256", "internalType": "uint256" }, + { + "name": "withdrawalShares", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setApproval", + "inputs": [ + { "name": "tokenID", "type": "uint256", "internalType": "uint256" }, + { "name": "operator", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setApprovalBridge", + "inputs": [ + { "name": "tokenID", "type": "uint256", "internalType": "uint256" }, + { "name": "operator", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" }, + { "name": "caller", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setApprovalForAll", + "inputs": [ + { "name": "operator", "type": "address", "internalType": "address" }, + { "name": "approved", "type": "bool", "internalType": "bool" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setGovernance", + "inputs": [ + { "name": "_who", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setPauser", + "inputs": [ + { "name": "", "type": "address", "internalType": "address" }, + { "name": "", "type": "bool", "internalType": "bool" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "sweep", + "inputs": [ + { + "name": "_target", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "symbol", + "inputs": [ + { "name": "tokenId", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "target0", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "target1", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "target2", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "target3", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "target4", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalShares", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [ + { "name": "tokenId", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { "name": "tokenID", "type": "uint256", "internalType": "uint256" }, + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFromBridge", + "inputs": [ + { "name": "tokenID", "type": "uint256", "internalType": "uint256" }, + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" }, + { "name": "caller", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "vault", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "vaultSharesToken", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "version", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "pure" + }, + { + "type": "event", + "name": "AddLiquidity", + "inputs": [ + { + "name": "provider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "lpAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "lpSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ApprovalForAll", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "operator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "approved", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CloseLong", + "inputs": [ + { + "name": "trader", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "destination", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "maturityTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "bondAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CloseShort", + "inputs": [ + { + "name": "trader", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "destination", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "maturityTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "basePayment", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "bondAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CollectGovernanceFee", + "inputs": [ + { + "name": "collector", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CreateCheckpoint", + "inputs": [ + { + "name": "checkpointTime", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "checkpointVaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "maturedShorts", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "maturedLongs", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialize", + "inputs": [ + { + "name": "provider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "lpAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "apr", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OpenLong", + "inputs": [ + { + "name": "trader", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "maturityTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "bondAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OpenShort", + "inputs": [ + { + "name": "trader", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assetId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "maturityTime", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "baseProceeds", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "bondAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PauseStatusUpdated", + "inputs": [ + { + "name": "isPaused", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RedeemWithdrawalShares", + "inputs": [ + { + "name": "provider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "destination", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "withdrawalShareAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RemoveLiquidity", + "inputs": [ + { + "name": "provider", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "destination", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "lpAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "vaultSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "asBase", + "type": "bool", + "indexed": false, + "internalType": "bool" + }, + { + "name": "withdrawalShareAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "lpSharePrice", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "extraData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Sweep", + "inputs": [ + { + "name": "collector", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "target", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TransferSingle", + "inputs": [ + { + "name": "operator", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "id", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { "type": "error", "name": "BatchInputLengthMismatch", "inputs": [] }, + { "type": "error", "name": "BelowMinimumContribution", "inputs": [] }, + { "type": "error", "name": "CircuitBreakerTriggered", "inputs": [] }, + { + "type": "error", + "name": "DecreasedPresentValueWhenAddingLiquidity", + "inputs": [] + }, + { "type": "error", "name": "DistributeExcessIdleFailed", "inputs": [] }, + { "type": "error", "name": "ExpInvalidExponent", "inputs": [] }, + { "type": "error", "name": "ExpiredDeadline", "inputs": [] }, + { "type": "error", "name": "InsufficientBalance", "inputs": [] }, + { "type": "error", "name": "InsufficientLiquidity", "inputs": [] }, + { "type": "error", "name": "InvalidApr", "inputs": [] }, + { "type": "error", "name": "InvalidCheckpointTime", "inputs": [] }, + { "type": "error", "name": "InvalidERC20Bridge", "inputs": [] }, + { "type": "error", "name": "InvalidEffectiveShareReserves", "inputs": [] }, + { "type": "error", "name": "InvalidFeeDestination", "inputs": [] }, + { "type": "error", "name": "InvalidInitialVaultSharePrice", "inputs": [] }, + { "type": "error", "name": "InvalidLPSharePrice", "inputs": [] }, + { "type": "error", "name": "InvalidPresentValue", "inputs": [] }, + { "type": "error", "name": "InvalidSignature", "inputs": [] }, + { "type": "error", "name": "InvalidTimestamp", "inputs": [] }, + { "type": "error", "name": "LnInvalidInput", "inputs": [] }, + { "type": "error", "name": "MinimumSharePrice", "inputs": [] }, + { "type": "error", "name": "MinimumTransactionAmount", "inputs": [] }, + { "type": "error", "name": "NotPayable", "inputs": [] }, + { "type": "error", "name": "OutputLimit", "inputs": [] }, + { "type": "error", "name": "PoolAlreadyInitialized", "inputs": [] }, + { "type": "error", "name": "PoolIsPaused", "inputs": [] }, + { "type": "error", "name": "RestrictedZeroAddress", "inputs": [] }, + { + "type": "error", + "name": "ReturnData", + "inputs": [{ "name": "data", "type": "bytes", "internalType": "bytes" }] + }, + { "type": "error", "name": "SweepFailed", "inputs": [] }, + { "type": "error", "name": "TransferFailed", "inputs": [] }, + { "type": "error", "name": "Unauthorized", "inputs": [] }, + { "type": "error", "name": "UnexpectedSuccess", "inputs": [] }, + { "type": "error", "name": "UnsafeCastToInt128", "inputs": [] }, + { "type": "error", "name": "UnsafeCastToInt256", "inputs": [] }, + { "type": "error", "name": "UnsafeCastToUint112", "inputs": [] }, + { "type": "error", "name": "UnsafeCastToUint128", "inputs": [] }, + { "type": "error", "name": "UnsafeCastToUint256", "inputs": [] }, + { "type": "error", "name": "UnsupportedToken", "inputs": [] }, + { "type": "error", "name": "UpdateLiquidityFailed", "inputs": [] } +] \ No newline at end of file diff --git a/abi/IMorpho.json b/abi/IMorpho.json new file mode 100644 index 0000000..493beca --- /dev/null +++ b/abi/IMorpho.json @@ -0,0 +1,630 @@ +[ + { + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "accrueInterest", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "borrow", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "shares", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "receiver", "type": "address", "internalType": "address" } + ], + "outputs": [ + { + "name": "assetsBorrowed", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "sharesBorrowed", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "createMarket", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "enableIrm", + "inputs": [ + { "name": "irm", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "enableLltv", + "inputs": [ + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "extSloads", + "inputs": [ + { "name": "slots", "type": "bytes32[]", "internalType": "bytes32[]" } + ], + "outputs": [ + { "name": "", "type": "bytes32[]", "internalType": "bytes32[]" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "feeRecipient", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashLoan", + "inputs": [ + { "name": "token", "type": "address", "internalType": "address" }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "idToMarketParams", + "inputs": [{ "name": "id", "type": "bytes32", "internalType": "Id" }], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isAuthorized", + "inputs": [ + { "name": "authorizer", "type": "address", "internalType": "address" }, + { "name": "authorized", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isIrmEnabled", + "inputs": [ + { "name": "irm", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isLltvEnabled", + "inputs": [ + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "liquidate", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "borrower", "type": "address", "internalType": "address" }, + { + "name": "seizedAssets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "repaidShares", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" }, + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "market", + "inputs": [{ "name": "id", "type": "bytes32", "internalType": "Id" }], + "outputs": [ + { + "name": "m", + "type": "tuple", + "internalType": "struct Market", + "components": [ + { + "name": "totalSupplyAssets", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "totalSupplyShares", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "totalBorrowAssets", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "totalBorrowShares", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "lastUpdate", + "type": "uint128", + "internalType": "uint128" + }, + { "name": "fee", "type": "uint128", "internalType": "uint128" } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonce", + "inputs": [ + { "name": "authorizer", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "position", + "inputs": [ + { "name": "id", "type": "bytes32", "internalType": "Id" }, + { "name": "user", "type": "address", "internalType": "address" } + ], + "outputs": [ + { + "name": "p", + "type": "tuple", + "internalType": "struct Position", + "components": [ + { + "name": "supplyShares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "borrowShares", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "collateral", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "repay", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "shares", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { + "name": "assetsRepaid", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "sharesRepaid", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setAuthorization", + "inputs": [ + { "name": "authorized", "type": "address", "internalType": "address" }, + { "name": "newIsAuthorized", "type": "bool", "internalType": "bool" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setAuthorizationWithSig", + "inputs": [ + { + "name": "authorization", + "type": "tuple", + "internalType": "struct Authorization", + "components": [ + { + "name": "authorizer", + "type": "address", + "internalType": "address" + }, + { + "name": "authorized", + "type": "address", + "internalType": "address" + }, + { "name": "isAuthorized", "type": "bool", "internalType": "bool" }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "name": "signature", + "type": "tuple", + "internalType": "struct Signature", + "components": [ + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFee", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "newFee", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFeeRecipient", + "inputs": [ + { + "name": "newFeeRecipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setOwner", + "inputs": [ + { "name": "newOwner", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "supply", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "shares", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { + "name": "assetsSupplied", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "sharesSupplied", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "supplyCollateral", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "shares", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "receiver", "type": "address", "internalType": "address" } + ], + "outputs": [ + { + "name": "assetsWithdrawn", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "sharesWithdrawn", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdrawCollateral", + "inputs": [ + { + "name": "marketParams", + "type": "tuple", + "internalType": "struct MarketParams", + "components": [ + { + "name": "loanToken", + "type": "address", + "internalType": "address" + }, + { + "name": "collateralToken", + "type": "address", + "internalType": "address" + }, + { "name": "oracle", "type": "address", "internalType": "address" }, + { "name": "irm", "type": "address", "internalType": "address" }, + { "name": "lltv", "type": "uint256", "internalType": "uint256" } + ] + }, + { "name": "assets", "type": "uint256", "internalType": "uint256" }, + { "name": "onBehalf", "type": "address", "internalType": "address" }, + { "name": "receiver", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] \ No newline at end of file diff --git a/abi/hyperdrive_registry.json b/abi/hyperdrive_registry.json new file mode 100644 index 0000000..c1ea11b --- /dev/null +++ b/abi/hyperdrive_registry.json @@ -0,0 +1,555 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "EndIndexTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidFactory", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidIndexes", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "admin", + "type": "address" + }], + "name": "AdminUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "data", + "type": "uint256" + }], + "name": "FactoryInfoUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "instance", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }], + "name": "InstanceInfoUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_endIndex", + "type": "uint256" + }], + "name": "getFactoriesInRange", + "outputs": [ + { + "internalType": "address[]", + "name": "factories", + "type": "address[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + }], + "name": "getFactoryAtIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }], + "name": "getFactoryInfo", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }], + "internalType": "struct IHyperdriveRegistry.FactoryInfo", + "name": "info", + "type": "tuple" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }], + "name": "getFactoryInfoWithMetadata", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "kind", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }], + "internalType": "struct IHyperdriveRegistry.FactoryInfoWithMetadata", + "name": "info", + "type": "tuple" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__factories", + "type": "address[]" + }], + "name": "getFactoryInfos", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }], + "internalType": "struct IHyperdriveRegistry.FactoryInfo[]", + "name": "info", + "type": "tuple[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__factories", + "type": "address[]" + }], + "name": "getFactoryInfosWithMetadata", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "kind", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }], + "internalType": "struct IHyperdriveRegistry.FactoryInfoWithMetadata[]", + "name": "info", + "type": "tuple[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + }], + "name": "getInstanceAtIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_instance", + "type": "address" + }], + "name": "getInstanceInfo", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }], + "internalType": "struct IHyperdriveRegistry.InstanceInfo", + "name": "info", + "type": "tuple" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_instance", + "type": "address" + }], + "name": "getInstanceInfoWithMetadata", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "kind", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }], + "internalType": "struct IHyperdriveRegistry.InstanceInfoWithMetadata", + "name": "info", + "type": "tuple" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__instances", + "type": "address[]" + }], + "name": "getInstanceInfos", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }], + "internalType": "struct IHyperdriveRegistry.InstanceInfo[]", + "name": "info", + "type": "tuple[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__instances", + "type": "address[]" + }], + "name": "getInstanceInfosWithMetadata", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "data", + "type": "uint256" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "kind", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }], + "internalType": "struct IHyperdriveRegistry.InstanceInfoWithMetadata[]", + "name": "info", + "type": "tuple[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_endIndex", + "type": "uint256" + }], + "name": "getInstancesInRange", + "outputs": [ + { + "internalType": "address[]", + "name": "instances", + "type": "address[]" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNumberOfFactories", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNumberOfInstances", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "kind", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__factories", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "_data", + "type": "uint128[]" + }], + "name": "setFactoryInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "__instances", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "_data", + "type": "uint128[]" + }, + { + "internalType": "address[]", + "name": "__factories", + "type": "address[]" + }], + "name": "setInstanceInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }], + "name": "updateAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }], + "stateMutability": "view", + "type": "function" + }] \ No newline at end of file diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py new file mode 100644 index 0000000..a15df1e --- /dev/null +++ b/constants/hyperdrive.py @@ -0,0 +1,23 @@ +import json + +HYPERDRIVE_REGISTRY = "0xbe082293b646cb619a638d29e8eff7cf2f46aa3a" + +HYPERDRIVE_ABI = None +with open("abi/IHyperdrive.json") as f: + HYPERDRIVE_ABI = json.load(f) + +HYPERDRIVE_MORPHO_ABI = None +with open("abi/IHyperdriveMorpho.json") as f: + HYPERDRIVE_MORPHO_ABI = json.load(f) + +HYPERDRIVE_REGISTRY_ABI = None +with open("abi/hyperdrive_registry.json") as f: + HYPERDRIVE_REGISTRY_ABI = json.load(f) + +MORPHO_ABI = None +with open("abi/IMorpho.json") as f: + MORPHO_ABI = json.load(f) + +ERC20_ABI = None +with open("abi/ERC20_abi.json") as f: + ERC20_ABI = json.load(f) diff --git a/test_hyperdrive.py b/test_hyperdrive.py new file mode 100644 index 0000000..b2d676d --- /dev/null +++ b/test_hyperdrive.py @@ -0,0 +1,50 @@ +# %% +import time +from decimal import Decimal + +import pandas as pd + +from constants.hyperdrive import ( + HYPERDRIVE_MORPHO_ABI, + HYPERDRIVE_REGISTRY, + HYPERDRIVE_REGISTRY_ABI, +) +from utils.hyperdrive import ( + get_hyperdrive_participants, + get_pool_details, + get_pool_positions, +) +from utils.web3_utils import w3 + +## Import +HYPERDRIVE_REGISTRY = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_REGISTRY), abi=HYPERDRIVE_REGISTRY_ABI) +number_of_instances = HYPERDRIVE_REGISTRY.functions.getNumberOfInstances().call() +instance_list = HYPERDRIVE_REGISTRY.functions.getInstancesInRange(0,number_of_instances).call() + +pool_to_test = instance_list[5] # 5 = ezETH, 3 = sUSDe/DAI +print(f"=== pool to test: {pool_to_test} ===") +start_time = time.time() +pool_users, pool_ids = get_hyperdrive_participants(pool_to_test, cache=True) +pool_to_test_contract = w3.eth.contract(address=w3.to_checksum_address(pool_to_test), abi=HYPERDRIVE_MORPHO_ABI) +config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_to_test_contract, debug=True) +print(f"=== {name} ===") +pool_positions = get_pool_positions( + pool_contract=pool_to_test_contract, + pool_users=pool_users, + pool_ids=pool_ids, + lp_rewardable_tvl=lp_rewardable_tvl, + short_rewardable_tvl=short_rewardable_tvl, + debug=True, +) + +# display stuff +pool_positions_df = pd.DataFrame(pool_positions, columns=["user","type","prefix","timestamp","balance","rewardable"]) # type: ignore +pool_positions_df = pool_positions_df.astype({'prefix': str, 'timestamp': str}) +pool_positions_df.loc['Total', 'balance'] = pool_positions_df['balance'].sum() +pool_positions_df.loc['Total', 'rewardable'] = pool_positions_df['rewardable'].sum() +total_rewardable = Decimal(pool_positions_df.loc['Total','rewardable']) +if vault_shares_balance == total_rewardable: + print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") +else: + print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") +print(pool_positions_df) diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py new file mode 100644 index 0000000..c630362 --- /dev/null +++ b/utils/hyperdrive.py @@ -0,0 +1,267 @@ +import itertools +import json +import os +from decimal import Decimal, getcontext +from enum import IntEnum + +import eth_abi +from dotenv import load_dotenv +from tqdm import tqdm + +from constants.hyperdrive import ( + ERC20_ABI, + HYPERDRIVE_ABI, + MORPHO_ABI, +) +from utils.web3_utils import ( + fetch_events_logs_with_retry, + w3, +) + +load_dotenv() + +PAGE_SIZE = 1900 +getcontext().prec = 100 # Set precision for Decimal calculations + +def get_first_contract_block(contract_address): + # do binary search up to latest block + latest_block = w3.eth.get_block_number() + earliest_block = 0 + while earliest_block < latest_block: + mid_block = (earliest_block + latest_block) // 2 + attempt_to_get_code = w3.eth.get_code(account=contract_address,block_identifier=mid_block) + if attempt_to_get_code == b'': + # Contract not yet deployed, continue searching in the later blocks + earliest_block = mid_block + 1 + else: + # Contract deployed, continue searching in the earlier blocks + latest_block = mid_block - 1 + # At this point, earliest_block and latest_block should be the same, + # and it represents the block where we can first retrieve the contract code. + assert earliest_block >= latest_block, f"something fucked up since {earliest_block=} isn't greater than or equal to {latest_block=}" + return earliest_block + +def get_hyperdrive_participants(pool, cache: bool = False): + target_block = w3.eth.get_block_number() + all_users = all_ids = start_block = None + if cache and os.path.exists(f"hyperdrive_users_{pool}.json"): + with open(f"hyperdrive_users_{pool}.json", "r") as f: + all_users = set(json.load(f)) + else: + all_users = set() + if cache and os.path.exists(f"hyperdrive_ids_{pool}.json"): + with open(f"hyperdrive_ids_{pool}.json", "r") as f: + all_ids = set(json.load(f)) + else: + all_ids = set() + if cache and os.path.exists(f"hyperdrive_latest_block_{pool}.json"): + with open(f"hyperdrive_latest_block_{pool}.json", "r") as f: + start_block = json.load(f) + 1 + if start_block >= target_block: + print(f"Skipping pool {pool} because it's up to date.") + return all_users, all_ids + else: + start_block = get_first_contract_block(pool) + assert all_users is not None, "error: all_users is None" + assert all_ids is not None, "error: all_ids is None" + assert start_block is not None, "error: start_block is None" + contract = w3.eth.contract(address=pool, abi=HYPERDRIVE_ABI) + + total_blocks_to_process = target_block - start_block + with tqdm(total=total_blocks_to_process, desc="Fetching Hyperdrive events", unit="block") as pbar: + current_block = start_block + while current_block < target_block: + to_block = min(current_block + PAGE_SIZE, target_block) + transfers = fetch_events_logs_with_retry( + label=f"Hyperdrive users {pool}", + contract_event=contract.events.TransferSingle(), + from_block=current_block, + to_block=to_block, + delay=0, + ) + for transfer in transfers: + all_users.add(transfer["args"]["to"]) + all_ids.add(transfer["args"]["id"]) + # Update the progress bar by the number of blocks processed in this iteration + blocks_processed = to_block - current_block + pbar.update(blocks_processed) + current_block = to_block + if cache: + with open(f"hyperdrive_users_{pool}.json", "w") as f: + json.dump(list(all_users), f) + with open(f"hyperdrive_ids_{pool}.json", "w") as f: + json.dump(list(all_ids), f) + with open(f"hyperdrive_latest_block_{pool}.json", "w") as f: + json.dump(target_block, f) + + return all_users, all_ids + +class HyperdrivePrefix(IntEnum): + r"""The asset ID is used to encode the trade type in a transaction receipt""" + + LP = 0 + LONG = 1 + SHORT = 2 + WITHDRAWAL_SHARE = 3 + +def convert_prefix_to_trade_type(prefix: int) -> str: + return HyperdrivePrefix(prefix).name + +def decode_asset_id(asset_id: int) -> tuple[int, int]: + r"""Decodes a transaction asset ID into its constituent parts of an identifier, data, and a timestamp. + + First calculate the prefix mask by left-shifting 1 by 248 bits and subtracting 1 from the result. + This gives us a bit-mask with 248 bits set to 1 and the rest set to 0. + Then apply this mask to the input ID using the bitwise-and operator `&` to extract + the lower 248 bits as the timestamp. + + The prefix is a unique asset ID which denotes the following trade types: + LP = 0 + LONG = 1 + SHORT = 2 + WITHDRAWAL_SHARE = 3 + + Arguments + --------- + asset_id: int + Encoded ID from a transaction. It is a concatenation, [identifier: 8 bits][timestamp: 248 bits] + + Returns + ------- + tuple[int, int] + identifier, timestamp + """ + prefix_mask = (1 << 248) - 1 + prefix = asset_id >> 248 # shr 248 bits + timestamp = asset_id & prefix_mask # apply the prefix mask + return prefix, timestamp + +def get_pool_details(pool_contract, debug: bool = False): + name = pool_contract.functions.name().call() + config_values = pool_contract.functions.getPoolConfig().call() + config_outputs = pool_contract.functions.getPoolConfig().abi['outputs'][0]['components'] + config_keys = [i['name'] for i in config_outputs if 'name' in i] + config = dict(zip(config_keys, config_values)) + if debug: + print(f"POOL {pool_contract.address[:8]} ({name}) CONFIG:") + for k,i in config.items(): + print(f" {k:<31} = {i}") + info_values = pool_contract.functions.getPoolInfo().call(block_identifier=20684260) + info_outputs = pool_contract.functions.getPoolInfo().abi['outputs'][0]['components'] + info_keys = [i['name'] for i in info_outputs if 'name' in i] + info = dict(zip(info_keys, info_values)) + if debug: + print(f"POOL {pool_contract.address[:8]} ({name}) INFO:") + for k,i in info.items(): + print(f" {k:<31} = {i}") + lp_short_positions = info['longExposure'] + + # query pool holdings of the base token + base_token_balance = None + if config["baseToken"] == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": + # the base token is ETH + base_token_balance = w3.eth.get_balance(pool_contract.address) + else: + base_token_contract = w3.eth.contract(address=config["baseToken"], abi=ERC20_ABI) + base_token_balance = base_token_contract.functions.balanceOf(pool_contract.address).call() + vault_shares_balance = vault_contract_address = vault_contract = None + if "Morpho" in name: + vault_contract_address = pool_contract.functions.vault().call() + print(f"{vault_contract_address=}") + vault_contract = w3.eth.contract(address=vault_contract_address, abi=MORPHO_ABI) + morpho_market_id = w3.keccak(eth_abi.encode( # type: ignore + ("address", "address", "address", "address", "uint256"), + ( + config["baseToken"], + pool_contract.functions.collateralToken().call(), + pool_contract.functions.oracle().call(), + pool_contract.functions.irm().call(), + pool_contract.functions.lltv().call(), + ), + )) + vault_shares_balance = vault_contract.functions.position(morpho_market_id,pool_contract.address).call()[0] + elif config["vaultSharesToken"] != "0x0000000000000000000000000000000000000000": + vault_shares_contract = w3.eth.contract(address=config["vaultSharesToken"], abi=ERC20_ABI) + vault_shares_balance = vault_shares_contract.functions.balanceOf(pool_contract.address).call() + short_rewardable_tvl = info['shortsOutstanding'] + lp_rewardable_tvl = vault_shares_balance - short_rewardable_tvl + if debug: + print(" === calculated values ===") + print(f" {'base_token_balance':<31} = {base_token_balance}") + if "Morpho" in name: + print(f" {'vault_contract':<31} = {vault_contract_address}") + print(f" {'vault_shares_balance':<31} = {vault_shares_balance}") + # if vault_shares_balance: + # vault_shares_balance_minus_shorts = vault_shares_balance - info['shortsOutstanding'] + # print(f" {'vault_shares_balance_minus_shorts':<31} = {vault_shares_balance_minus_shorts/1e18 if vault_shares_balance_minus_shorts else vault_shares_balance_minus_shorts}") + print(f" {'lp_short_positions':<31} = {lp_short_positions}") + print(f" {'lp_rewardable_tvl':<31} = {lp_rewardable_tvl}") + print(f" {'short_rewardable_tvl':<31} = {short_rewardable_tvl}") + + return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl + + return config, info, adjusted_share_reserves, lp_ratio, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl + +def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, debug: bool = False): + # sourcery skip: extract-method + pool_positions = [] + rewardable_by_prefix = { + 0: Decimal(lp_rewardable_tvl), # LPs and Withdrawal Shares get a share of the LP rewardable TVL + 1: Decimal(0), # longs get nothing since they have no exposure to the variable rate + 2: Decimal(short_rewardable_tvl), # shorts get a share of the Short rewardable TVL + 3: Decimal(lp_rewardable_tvl), # withdrawal shares get rewarded the same as LPs + } + bal_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} + shares_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} + for user,id in itertools.product(pool_users, pool_ids): + trade_type, prefix, timestamp = get_trade_details(int(id)) + bal = pool_contract.functions.balanceOf(int(id),user).call() + if bal > Decimal(1): + if debug: + print(f"user={user[:8]} {trade_type:<4}({prefix=}) {timestamp=:>12} balance={bal:>32}") + pool_positions.append([user, trade_type, prefix, timestamp, bal, Decimal(0)]) + bal_by_prefix[prefix] += bal + for position in pool_positions: + prefix = position[2] + if bal_by_prefix[prefix] != Decimal(0): + # calculate their share of of total balances by prefix + share_of_rewardable = position[4] / bal_by_prefix[prefix] + position[5] = (rewardable_by_prefix[prefix] * share_of_rewardable).quantize(Decimal('0')) + + # Calculate shares by prefix + shares_by_prefix = {prefix: sum(position[5] for position in pool_positions if position[2] == prefix) for prefix in range(4)} + + # Correction step + combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately + for prefixes in combined_prefixes: + combined_shares = sum(shares_by_prefix[p] for p in prefixes) + combined_rewardable = rewardable_by_prefix[prefixes[0]] # take rewardable_by_prefix of first prefix + print(f"{prefixes=}") + print(f"{combined_shares=}") + print(f"{combined_rewardable=}") + if combined_shares != combined_rewardable: + diff = combined_rewardable - combined_shares + # Find the position with the largest share among the combined prefixes + max_position = max((p for p in pool_positions if p[2] in prefixes), key=lambda x: x[5]) + print(f"found {diff=} in {prefixes=}, adjusting\n{max_position=}") + max_position[5] += diff + print(f"{max_position=}") + + # Re-calculate shares by prefix + shares_by_prefix = {prefix: sum(position[5] for position in pool_positions if position[2] == prefix) for prefix in range(4)} + + # combine LPs and withdrawal shares into subtotals to check accuracy + subtotals_by_prefix = {0: shares_by_prefix[0] + shares_by_prefix[3], 1: shares_by_prefix[1], 2: shares_by_prefix[2]} + # each subtotal (shares_by_prefix) should match the total (rewardable_by_prefix) + for prefix,subtotal in subtotals_by_prefix.items(): + print(f"{prefix=:<3} balance={bal_by_prefix[prefix]:>24} shares={subtotal:>24} rewardable={rewardable_by_prefix[prefix]:>24}") + if subtotal == rewardable_by_prefix[prefix]: + print(f" check subtotals_by_prefix == rewardable_by_prefix ({subtotal} == {rewardable_by_prefix[prefix]}) ✅") + else: + print(f" check subtotals_by_prefix == rewardable_by_prefix ({subtotal} != {rewardable_by_prefix[prefix]}) ❌") + return pool_positions + +def get_trade_details(asset_id: int) -> tuple[str, int, int]: + prefix, timestamp = decode_asset_id(asset_id) + trade_type = convert_prefix_to_trade_type(prefix) + return trade_type, prefix, timestamp From 37c4357f1ca79385d2132a413260e360e44d5ecc Mon Sep 17 00:00:00 2001 From: Mihai Date: Tue, 1 Oct 2024 22:36:43 -0400 Subject: [PATCH 05/19] remove debug from get_pool_details --- constants/hyperdrive.py | 4 ---- test_hyperdrive.py | 2 +- utils/hyperdrive.py | 38 ++++---------------------------------- 3 files changed, 5 insertions(+), 39 deletions(-) diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py index a15df1e..add2338 100644 --- a/constants/hyperdrive.py +++ b/constants/hyperdrive.py @@ -2,10 +2,6 @@ HYPERDRIVE_REGISTRY = "0xbe082293b646cb619a638d29e8eff7cf2f46aa3a" -HYPERDRIVE_ABI = None -with open("abi/IHyperdrive.json") as f: - HYPERDRIVE_ABI = json.load(f) - HYPERDRIVE_MORPHO_ABI = None with open("abi/IHyperdriveMorpho.json") as f: HYPERDRIVE_MORPHO_ABI = json.load(f) diff --git a/test_hyperdrive.py b/test_hyperdrive.py index b2d676d..3734ad2 100644 --- a/test_hyperdrive.py +++ b/test_hyperdrive.py @@ -26,7 +26,7 @@ start_time = time.time() pool_users, pool_ids = get_hyperdrive_participants(pool_to_test, cache=True) pool_to_test_contract = w3.eth.contract(address=w3.to_checksum_address(pool_to_test), abi=HYPERDRIVE_MORPHO_ABI) -config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_to_test_contract, debug=True) +config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_to_test_contract) print(f"=== {name} ===") pool_positions = get_pool_positions( pool_contract=pool_to_test_contract, diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index c630362..a239d56 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -10,7 +10,7 @@ from constants.hyperdrive import ( ERC20_ABI, - HYPERDRIVE_ABI, + HYPERDRIVE_MORPHO_ABI, MORPHO_ABI, ) from utils.web3_utils import ( @@ -65,7 +65,7 @@ def get_hyperdrive_participants(pool, cache: bool = False): assert all_users is not None, "error: all_users is None" assert all_ids is not None, "error: all_ids is None" assert start_block is not None, "error: start_block is None" - contract = w3.eth.contract(address=pool, abi=HYPERDRIVE_ABI) + contract = w3.eth.contract(address=pool, abi=HYPERDRIVE_MORPHO_ABI) total_blocks_to_process = target_block - start_block with tqdm(total=total_blocks_to_process, desc="Fetching Hyperdrive events", unit="block") as pbar: @@ -136,34 +136,18 @@ def decode_asset_id(asset_id: int) -> tuple[int, int]: timestamp = asset_id & prefix_mask # apply the prefix mask return prefix, timestamp -def get_pool_details(pool_contract, debug: bool = False): +def get_pool_details(pool_contract): name = pool_contract.functions.name().call() config_values = pool_contract.functions.getPoolConfig().call() config_outputs = pool_contract.functions.getPoolConfig().abi['outputs'][0]['components'] config_keys = [i['name'] for i in config_outputs if 'name' in i] config = dict(zip(config_keys, config_values)) - if debug: - print(f"POOL {pool_contract.address[:8]} ({name}) CONFIG:") - for k,i in config.items(): - print(f" {k:<31} = {i}") info_values = pool_contract.functions.getPoolInfo().call(block_identifier=20684260) info_outputs = pool_contract.functions.getPoolInfo().abi['outputs'][0]['components'] info_keys = [i['name'] for i in info_outputs if 'name' in i] info = dict(zip(info_keys, info_values)) - if debug: - print(f"POOL {pool_contract.address[:8]} ({name}) INFO:") - for k,i in info.items(): - print(f" {k:<31} = {i}") - lp_short_positions = info['longExposure'] - # query pool holdings of the base token - base_token_balance = None - if config["baseToken"] == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE": - # the base token is ETH - base_token_balance = w3.eth.get_balance(pool_contract.address) - else: - base_token_contract = w3.eth.contract(address=config["baseToken"], abi=ERC20_ABI) - base_token_balance = base_token_contract.functions.balanceOf(pool_contract.address).call() + # query pool holdings vault_shares_balance = vault_contract_address = vault_contract = None if "Morpho" in name: vault_contract_address = pool_contract.functions.vault().call() @@ -185,23 +169,9 @@ def get_pool_details(pool_contract, debug: bool = False): vault_shares_balance = vault_shares_contract.functions.balanceOf(pool_contract.address).call() short_rewardable_tvl = info['shortsOutstanding'] lp_rewardable_tvl = vault_shares_balance - short_rewardable_tvl - if debug: - print(" === calculated values ===") - print(f" {'base_token_balance':<31} = {base_token_balance}") - if "Morpho" in name: - print(f" {'vault_contract':<31} = {vault_contract_address}") - print(f" {'vault_shares_balance':<31} = {vault_shares_balance}") - # if vault_shares_balance: - # vault_shares_balance_minus_shorts = vault_shares_balance - info['shortsOutstanding'] - # print(f" {'vault_shares_balance_minus_shorts':<31} = {vault_shares_balance_minus_shorts/1e18 if vault_shares_balance_minus_shorts else vault_shares_balance_minus_shorts}") - print(f" {'lp_short_positions':<31} = {lp_short_positions}") - print(f" {'lp_rewardable_tvl':<31} = {lp_rewardable_tvl}") - print(f" {'short_rewardable_tvl':<31} = {short_rewardable_tvl}") return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl - return config, info, adjusted_share_reserves, lp_ratio, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl - def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, debug: bool = False): # sourcery skip: extract-method pool_positions = [] From 07c6d13baa216bb043ee07f3b571071ac01de51a Mon Sep 17 00:00:00 2001 From: Mihai Date: Tue, 1 Oct 2024 22:44:24 -0400 Subject: [PATCH 06/19] remove silly function --- constants/hyperdrive.py | 9 +++++++++ utils/hyperdrive.py | 15 ++------------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py index add2338..d6493b4 100644 --- a/constants/hyperdrive.py +++ b/constants/hyperdrive.py @@ -1,4 +1,5 @@ import json +from enum import IntEnum HYPERDRIVE_REGISTRY = "0xbe082293b646cb619a638d29e8eff7cf2f46aa3a" @@ -17,3 +18,11 @@ ERC20_ABI = None with open("abi/ERC20_abi.json") as f: ERC20_ABI = json.load(f) + +class HyperdrivePrefix(IntEnum): + r"""The asset ID is used to encode the trade type in a transaction receipt""" + + LP = 0 + LONG = 1 + SHORT = 2 + WITHDRAWAL_SHARE = 3 diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index a239d56..014a38f 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -2,7 +2,6 @@ import json import os from decimal import Decimal, getcontext -from enum import IntEnum import eth_abi from dotenv import load_dotenv @@ -12,6 +11,7 @@ ERC20_ABI, HYPERDRIVE_MORPHO_ABI, MORPHO_ABI, + HyperdrivePrefix ) from utils.web3_utils import ( fetch_events_logs_with_retry, @@ -96,17 +96,6 @@ def get_hyperdrive_participants(pool, cache: bool = False): return all_users, all_ids -class HyperdrivePrefix(IntEnum): - r"""The asset ID is used to encode the trade type in a transaction receipt""" - - LP = 0 - LONG = 1 - SHORT = 2 - WITHDRAWAL_SHARE = 3 - -def convert_prefix_to_trade_type(prefix: int) -> str: - return HyperdrivePrefix(prefix).name - def decode_asset_id(asset_id: int) -> tuple[int, int]: r"""Decodes a transaction asset ID into its constituent parts of an identifier, data, and a timestamp. @@ -233,5 +222,5 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s def get_trade_details(asset_id: int) -> tuple[str, int, int]: prefix, timestamp = decode_asset_id(asset_id) - trade_type = convert_prefix_to_trade_type(prefix) + trade_type = HyperdrivePrefix(prefix).name return trade_type, prefix, timestamp From 17c9bc7aabae650e7f2cd60873ab3a71fe3e2f22 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 17 Oct 2024 02:35:54 -0400 Subject: [PATCH 07/19] update --- constants/hyperdrive.py | 6 +- test_hyperdrive.py | 40 +++------ utils/hyperdrive.py | 183 ++++++++++++++-------------------------- 3 files changed, 76 insertions(+), 153 deletions(-) diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py index d6493b4..8a0c963 100644 --- a/constants/hyperdrive.py +++ b/constants/hyperdrive.py @@ -1,16 +1,12 @@ import json from enum import IntEnum -HYPERDRIVE_REGISTRY = "0xbe082293b646cb619a638d29e8eff7cf2f46aa3a" +HYPERDRIVE_SUSDE_POOL = "0x05b65FA90AD702e6Fd0C3Bd7c4c9C47BAB2BEa6b" HYPERDRIVE_MORPHO_ABI = None with open("abi/IHyperdriveMorpho.json") as f: HYPERDRIVE_MORPHO_ABI = json.load(f) -HYPERDRIVE_REGISTRY_ABI = None -with open("abi/hyperdrive_registry.json") as f: - HYPERDRIVE_REGISTRY_ABI = json.load(f) - MORPHO_ABI = None with open("abi/IMorpho.json") as f: MORPHO_ABI = json.load(f) diff --git a/test_hyperdrive.py b/test_hyperdrive.py index 3734ad2..9b2ad90 100644 --- a/test_hyperdrive.py +++ b/test_hyperdrive.py @@ -1,35 +1,18 @@ # %% -import time from decimal import Decimal -import pandas as pd - -from constants.hyperdrive import ( - HYPERDRIVE_MORPHO_ABI, - HYPERDRIVE_REGISTRY, - HYPERDRIVE_REGISTRY_ABI, -) -from utils.hyperdrive import ( - get_hyperdrive_participants, - get_pool_details, - get_pool_positions, -) +from constants.hyperdrive import HYPERDRIVE_MORPHO_ABI, HYPERDRIVE_SUSDE_POOL +from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions from utils.web3_utils import w3 ## Import -HYPERDRIVE_REGISTRY = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_REGISTRY), abi=HYPERDRIVE_REGISTRY_ABI) -number_of_instances = HYPERDRIVE_REGISTRY.functions.getNumberOfInstances().call() -instance_list = HYPERDRIVE_REGISTRY.functions.getInstancesInRange(0,number_of_instances).call() - -pool_to_test = instance_list[5] # 5 = ezETH, 3 = sUSDe/DAI -print(f"=== pool to test: {pool_to_test} ===") -start_time = time.time() -pool_users, pool_ids = get_hyperdrive_participants(pool_to_test, cache=True) -pool_to_test_contract = w3.eth.contract(address=w3.to_checksum_address(pool_to_test), abi=HYPERDRIVE_MORPHO_ABI) -config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_to_test_contract) +print(f"=== {HYPERDRIVE_SUSDE_POOL} ===") +pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL) +pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL), abi=HYPERDRIVE_MORPHO_ABI) +_, _, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) print(f"=== {name} ===") pool_positions = get_pool_positions( - pool_contract=pool_to_test_contract, + pool_contract=pool_contract, pool_users=pool_users, pool_ids=pool_ids, lp_rewardable_tvl=lp_rewardable_tvl, @@ -38,13 +21,10 @@ ) # display stuff -pool_positions_df = pd.DataFrame(pool_positions, columns=["user","type","prefix","timestamp","balance","rewardable"]) # type: ignore -pool_positions_df = pool_positions_df.astype({'prefix': str, 'timestamp': str}) -pool_positions_df.loc['Total', 'balance'] = pool_positions_df['balance'].sum() -pool_positions_df.loc['Total', 'rewardable'] = pool_positions_df['rewardable'].sum() -total_rewardable = Decimal(pool_positions_df.loc['Total','rewardable']) +total_rewardable = Decimal(sum(position[5] for position in pool_positions)) if vault_shares_balance == total_rewardable: print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") else: print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") -print(pool_positions_df) + +# %% diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index 014a38f..60a51f7 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -1,22 +1,10 @@ import itertools -import json -import os from decimal import Decimal, getcontext -import eth_abi from dotenv import load_dotenv -from tqdm import tqdm - -from constants.hyperdrive import ( - ERC20_ABI, - HYPERDRIVE_MORPHO_ABI, - MORPHO_ABI, - HyperdrivePrefix -) -from utils.web3_utils import ( - fetch_events_logs_with_retry, - w3, -) + +from constants.hyperdrive import ERC20_ABI,HYPERDRIVE_MORPHO_ABI,HyperdrivePrefix +from utils.web3_utils import fetch_events_logs_with_retry,w3 load_dotenv() @@ -41,58 +29,30 @@ def get_first_contract_block(contract_address): assert earliest_block >= latest_block, f"something fucked up since {earliest_block=} isn't greater than or equal to {latest_block=}" return earliest_block -def get_hyperdrive_participants(pool, cache: bool = False): +def get_hyperdrive_participants(pool): target_block = w3.eth.get_block_number() - all_users = all_ids = start_block = None - if cache and os.path.exists(f"hyperdrive_users_{pool}.json"): - with open(f"hyperdrive_users_{pool}.json", "r") as f: - all_users = set(json.load(f)) - else: - all_users = set() - if cache and os.path.exists(f"hyperdrive_ids_{pool}.json"): - with open(f"hyperdrive_ids_{pool}.json", "r") as f: - all_ids = set(json.load(f)) - else: - all_ids = set() - if cache and os.path.exists(f"hyperdrive_latest_block_{pool}.json"): - with open(f"hyperdrive_latest_block_{pool}.json", "r") as f: - start_block = json.load(f) + 1 - if start_block >= target_block: - print(f"Skipping pool {pool} because it's up to date.") - return all_users, all_ids - else: - start_block = get_first_contract_block(pool) + all_users = set() + all_ids = set() + start_block = get_first_contract_block(pool) assert all_users is not None, "error: all_users is None" assert all_ids is not None, "error: all_ids is None" assert start_block is not None, "error: start_block is None" contract = w3.eth.contract(address=pool, abi=HYPERDRIVE_MORPHO_ABI) - total_blocks_to_process = target_block - start_block - with tqdm(total=total_blocks_to_process, desc="Fetching Hyperdrive events", unit="block") as pbar: - current_block = start_block - while current_block < target_block: - to_block = min(current_block + PAGE_SIZE, target_block) - transfers = fetch_events_logs_with_retry( - label=f"Hyperdrive users {pool}", - contract_event=contract.events.TransferSingle(), - from_block=current_block, - to_block=to_block, - delay=0, - ) - for transfer in transfers: - all_users.add(transfer["args"]["to"]) - all_ids.add(transfer["args"]["id"]) - # Update the progress bar by the number of blocks processed in this iteration - blocks_processed = to_block - current_block - pbar.update(blocks_processed) - current_block = to_block - if cache: - with open(f"hyperdrive_users_{pool}.json", "w") as f: - json.dump(list(all_users), f) - with open(f"hyperdrive_ids_{pool}.json", "w") as f: - json.dump(list(all_ids), f) - with open(f"hyperdrive_latest_block_{pool}.json", "w") as f: - json.dump(target_block, f) + current_block = start_block + while current_block < target_block: + to_block = min(current_block + PAGE_SIZE, target_block) + transfers = fetch_events_logs_with_retry( + label=f"Hyperdrive users {pool}", + contract_event=contract.events.TransferSingle(), + from_block=current_block, + to_block=to_block, + delay=0, + ) + for transfer in transfers: + all_users.add(transfer["args"]["to"]) + all_ids.add(transfer["args"]["id"]) + current_block = to_block return all_users, all_ids @@ -131,29 +91,14 @@ def get_pool_details(pool_contract): config_outputs = pool_contract.functions.getPoolConfig().abi['outputs'][0]['components'] config_keys = [i['name'] for i in config_outputs if 'name' in i] config = dict(zip(config_keys, config_values)) - info_values = pool_contract.functions.getPoolInfo().call(block_identifier=20684260) + info_values = pool_contract.functions.getPoolInfo().call() info_outputs = pool_contract.functions.getPoolInfo().abi['outputs'][0]['components'] info_keys = [i['name'] for i in info_outputs if 'name' in i] info = dict(zip(info_keys, info_values)) # query pool holdings - vault_shares_balance = vault_contract_address = vault_contract = None - if "Morpho" in name: - vault_contract_address = pool_contract.functions.vault().call() - print(f"{vault_contract_address=}") - vault_contract = w3.eth.contract(address=vault_contract_address, abi=MORPHO_ABI) - morpho_market_id = w3.keccak(eth_abi.encode( # type: ignore - ("address", "address", "address", "address", "uint256"), - ( - config["baseToken"], - pool_contract.functions.collateralToken().call(), - pool_contract.functions.oracle().call(), - pool_contract.functions.irm().call(), - pool_contract.functions.lltv().call(), - ), - )) - vault_shares_balance = vault_contract.functions.position(morpho_market_id,pool_contract.address).call()[0] - elif config["vaultSharesToken"] != "0x0000000000000000000000000000000000000000": + vault_shares_balance = None + if config["vaultSharesToken"] != "0x0000000000000000000000000000000000000000": vault_shares_contract = w3.eth.contract(address=config["vaultSharesToken"], abi=ERC20_ABI) vault_shares_balance = vault_shares_contract.functions.balanceOf(pool_contract.address).call() short_rewardable_tvl = info['shortsOutstanding'] @@ -162,62 +107,64 @@ def get_pool_details(pool_contract): return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, debug: bool = False): - # sourcery skip: extract-method pool_positions = [] - rewardable_by_prefix = { - 0: Decimal(lp_rewardable_tvl), # LPs and Withdrawal Shares get a share of the LP rewardable TVL - 1: Decimal(0), # longs get nothing since they have no exposure to the variable rate - 2: Decimal(short_rewardable_tvl), # shorts get a share of the Short rewardable TVL - 3: Decimal(lp_rewardable_tvl), # withdrawal shares get rewarded the same as LPs - } + combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately bal_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} - shares_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} - for user,id in itertools.product(pool_users, pool_ids): + + # First pass: collect balances + for user, id in itertools.product(pool_users, pool_ids): trade_type, prefix, timestamp = get_trade_details(int(id)) - bal = pool_contract.functions.balanceOf(int(id),user).call() + bal = pool_contract.functions.balanceOf(int(id), user).call() if bal > Decimal(1): if debug: print(f"user={user[:8]} {trade_type:<4}({prefix=}) {timestamp=:>12} balance={bal:>32}") pool_positions.append([user, trade_type, prefix, timestamp, bal, Decimal(0)]) bal_by_prefix[prefix] += bal + # manually hard-code a withdrawal share position + # bal = 24101344855221864785272839529 + # pool_positions.append(["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "WITHDRAWAL_SHARE", 3, 1678908800, bal, Decimal(0)]) + # bal_by_prefix[3] += bal + + # Second pass: calculate shares (prefix 1 (longs) get nothing, so we skip it) for position in pool_positions: prefix = position[2] - if bal_by_prefix[prefix] != Decimal(0): - # calculate their share of of total balances by prefix - share_of_rewardable = position[4] / bal_by_prefix[prefix] - position[5] = (rewardable_by_prefix[prefix] * share_of_rewardable).quantize(Decimal('0')) - - # Calculate shares by prefix - shares_by_prefix = {prefix: sum(position[5] for position in pool_positions if position[2] == prefix) for prefix in range(4)} - - # Correction step - combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately + if prefix in [0, 3]: # assign rewards for LPs and withdrawal shares + combined_lp_balance = bal_by_prefix[0] + bal_by_prefix[3] # combine LP and withdrawal share balance + if combined_lp_balance != Decimal(0): + share_of_rewardable = position[4] / combined_lp_balance + position[5] = (lp_rewardable_tvl * share_of_rewardable).quantize(Decimal('0')) + elif prefix == 2: # assign rewards for shorts + if bal_by_prefix[2] != Decimal(0): + share_of_rewardable = position[4] / bal_by_prefix[2] + position[5] = (short_rewardable_tvl * share_of_rewardable).quantize(Decimal('0')) + + # Correction step to fix rounding errors for prefixes in combined_prefixes: - combined_shares = sum(shares_by_prefix[p] for p in prefixes) - combined_rewardable = rewardable_by_prefix[prefixes[0]] # take rewardable_by_prefix of first prefix - print(f"{prefixes=}") - print(f"{combined_shares=}") - print(f"{combined_rewardable=}") + combined_shares = sum(position[5] for position in pool_positions if position[2] in prefixes) + combined_rewardable = lp_rewardable_tvl if prefixes[0] == 0 else short_rewardable_tvl + if debug: + print(f"{prefixes=}") + print(f"{combined_shares=}") + print(f"{combined_rewardable=}") if combined_shares != combined_rewardable: diff = combined_rewardable - combined_shares # Find the position with the largest share among the combined prefixes max_position = max((p for p in pool_positions if p[2] in prefixes), key=lambda x: x[5]) - print(f"found {diff=} in {prefixes=}, adjusting\n{max_position=}") + if debug: + print(f"found {diff=} in {prefixes=}, adjusting\n{max_position=}") max_position[5] += diff - print(f"{max_position=}") - - # Re-calculate shares by prefix - shares_by_prefix = {prefix: sum(position[5] for position in pool_positions if position[2] == prefix) for prefix in range(4)} - - # combine LPs and withdrawal shares into subtotals to check accuracy - subtotals_by_prefix = {0: shares_by_prefix[0] + shares_by_prefix[3], 1: shares_by_prefix[1], 2: shares_by_prefix[2]} - # each subtotal (shares_by_prefix) should match the total (rewardable_by_prefix) - for prefix,subtotal in subtotals_by_prefix.items(): - print(f"{prefix=:<3} balance={bal_by_prefix[prefix]:>24} shares={subtotal:>24} rewardable={rewardable_by_prefix[prefix]:>24}") - if subtotal == rewardable_by_prefix[prefix]: - print(f" check subtotals_by_prefix == rewardable_by_prefix ({subtotal} == {rewardable_by_prefix[prefix]}) ✅") + if debug: + print(f"{max_position=}") + + # Make sure rewards add up to rewardable TVL + for prefixes in combined_prefixes: + combined_shares = sum(position[5] for position in pool_positions if position[2] in prefixes) + combined_rewardable = lp_rewardable_tvl if prefixes[0] == 0 else short_rewardable_tvl + if combined_shares == combined_rewardable: + print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} == {combined_rewardable}) ✅") else: - print(f" check subtotals_by_prefix == rewardable_by_prefix ({subtotal} != {rewardable_by_prefix[prefix]}) ❌") + print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} != {combined_rewardable}) ❌") + return pool_positions def get_trade_details(asset_id: int) -> tuple[str, int, int]: From ef31d115e104bd9f98b3456d1a62a255e793b98b Mon Sep 17 00:00:00 2001 From: Mihai Date: Sun, 20 Oct 2024 01:09:14 -0400 Subject: [PATCH 08/19] make a class --- constants/hyperdrive.py | 3 +- constants/integration_ids.py | 3 ++ integrations/hyperdrive.py | 75 ++++++++++++++++++++++++++++++++++++ test_hyperdrive.py | 8 ++-- test_hyperdrive2.py | 5 +++ utils/hyperdrive.py | 4 +- 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 integrations/hyperdrive.py create mode 100644 test_hyperdrive2.py diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py index 8a0c963..8852a70 100644 --- a/constants/hyperdrive.py +++ b/constants/hyperdrive.py @@ -1,7 +1,8 @@ import json from enum import IntEnum -HYPERDRIVE_SUSDE_POOL = "0x05b65FA90AD702e6Fd0C3Bd7c4c9C47BAB2BEa6b" +HYPERDRIVE_SUSDE_POOL_ADDRESS = "0x05b65FA90AD702e6Fd0C3Bd7c4c9C47BAB2BEa6b" +HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK = 20931644 HYPERDRIVE_MORPHO_ABI = None with open("abi/IHyperdriveMorpho.json") as f: diff --git a/constants/integration_ids.py b/constants/integration_ids.py index f4e91ab..5db8d9f 100644 --- a/constants/integration_ids.py +++ b/constants/integration_ids.py @@ -136,6 +136,9 @@ class IntegrationID(Enum): # Inverse Finance FiRM FIRM_SUSDE = ('firm_susde', 'Inverse Finance FiRM sUSDe', Token.SUSDE) + # Hyperdrive + HYPERDRIVE_SUSDE = ('hyperdrive_susde', 'ElementDAO 182 Day sUSDe Hyperdrive', Token.SUSDE) + def __init__(self, column_name: str, description: str, token: Token = Token.USDE): self.column_name = column_name self.description = description diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py new file mode 100644 index 0000000..005d708 --- /dev/null +++ b/integrations/hyperdrive.py @@ -0,0 +1,75 @@ +from typing import List, Optional, Set +from decimal import Decimal +from constants.chains import Chain +from models.integration import Integration +from constants.integration_ids import IntegrationID +from constants.hyperdrive import HYPERDRIVE_SUSDE_POOL_ADDRESS, HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, HYPERDRIVE_MORPHO_ABI +from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions +from utils.web3_utils import w3 + +class Hyperdrive(Integration): + def __init__(self): + super().__init__( + IntegrationID.HYPERDRIVE_SUSDE, + HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, + Chain.ETHEREUM, + None, + 20, + 1, + None, + None, + ) + self.pool_ids = None + self.pool_users = None + self.pool_positions = None + + def update_participants(self): + self.pool_users, self.pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) + + def get_participants(self, blocks: Optional[List[int]]) -> Set[str]: + if self.pool_users is None: + self.update_participants() + return self.pool_users + + def get_balance(self, user: str, block: int) -> float: + # update hyperdrive participants + if self.pool_positions is None: + if self.pool_users is None: + self.update_participants() + # get pool positions + pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) + _, _, _, _, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) + self.pool_positions = get_pool_positions( + pool_contract=pool_contract, + pool_users=self.pool_users, + pool_ids=self.pool_ids, + lp_rewardable_tvl=lp_rewardable_tvl, + short_rewardable_tvl=short_rewardable_tvl, + block=block, + debug=True, + ) + # get the user's balance + rewardable_tvl = sum(position[5] for position in self.pool_positions if position[0] == user) + return rewardable_tvl / 1e18 + + def test_hyperdrive(self): + print(f"=== {HYPERDRIVE_SUSDE_POOL_ADDRESS} ===") + pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) + pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) + _, _, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) + print(f"=== {name} ===") + pool_positions = get_pool_positions( + pool_contract=pool_contract, + pool_users=pool_users, + pool_ids=pool_ids, + lp_rewardable_tvl=lp_rewardable_tvl, + short_rewardable_tvl=short_rewardable_tvl, + debug=True, + ) + + # display stuff + total_rewardable = Decimal(sum(position[5] for position in pool_positions)) + if vault_shares_balance == total_rewardable: + print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") + else: + print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") \ No newline at end of file diff --git a/test_hyperdrive.py b/test_hyperdrive.py index 9b2ad90..4315052 100644 --- a/test_hyperdrive.py +++ b/test_hyperdrive.py @@ -1,14 +1,14 @@ # %% from decimal import Decimal -from constants.hyperdrive import HYPERDRIVE_MORPHO_ABI, HYPERDRIVE_SUSDE_POOL +from constants.hyperdrive import HYPERDRIVE_MORPHO_ABI, HYPERDRIVE_SUSDE_POOL_ADDRESS from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions from utils.web3_utils import w3 ## Import -print(f"=== {HYPERDRIVE_SUSDE_POOL} ===") -pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL) -pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL), abi=HYPERDRIVE_MORPHO_ABI) +print(f"=== {HYPERDRIVE_SUSDE_POOL_ADDRESS} ===") +pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) +pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) _, _, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) print(f"=== {name} ===") pool_positions = get_pool_positions( diff --git a/test_hyperdrive2.py b/test_hyperdrive2.py new file mode 100644 index 0000000..589e996 --- /dev/null +++ b/test_hyperdrive2.py @@ -0,0 +1,5 @@ +# %% +from integrations.hyperdrive import Hyperdrive + +hyperdrive = Hyperdrive() +hyperdrive.test_hyperdrive() \ No newline at end of file diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index 60a51f7..5a875d7 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -106,7 +106,7 @@ def get_pool_details(pool_contract): return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl -def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, debug: bool = False): +def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, block = None, debug: bool = False): pool_positions = [] combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately bal_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} @@ -114,7 +114,7 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s # First pass: collect balances for user, id in itertools.product(pool_users, pool_ids): trade_type, prefix, timestamp = get_trade_details(int(id)) - bal = pool_contract.functions.balanceOf(int(id), user).call() + bal = pool_contract.functions.balanceOf(int(id), user).call(block_identifier=block or "latest") if bal > Decimal(1): if debug: print(f"user={user[:8]} {trade_type:<4}({prefix=}) {timestamp=:>12} balance={bal:>32}") From e20400f92560b55f1dd1a790c0fce1e2716bb1d1 Mon Sep 17 00:00:00 2001 From: Mihai Date: Sun, 20 Oct 2024 01:55:15 -0400 Subject: [PATCH 09/19] remove debug, add start block, simplify test --- integrations/hyperdrive.py | 17 +++++++++-------- test_hyperdrive.py | 31 +++---------------------------- test_hyperdrive2.py | 5 ----- utils/hyperdrive.py | 16 +++------------- 4 files changed, 15 insertions(+), 54 deletions(-) delete mode 100644 test_hyperdrive2.py diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 005d708..66842ec 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -24,7 +24,10 @@ def __init__(self): self.pool_positions = None def update_participants(self): - self.pool_users, self.pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) + self.pool_users, self.pool_ids = get_hyperdrive_participants( + pool=HYPERDRIVE_SUSDE_POOL_ADDRESS, + start_block=HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, + ) def get_participants(self, blocks: Optional[List[int]]) -> Set[str]: if self.pool_users is None: @@ -46,28 +49,26 @@ def get_balance(self, user: str, block: int) -> float: lp_rewardable_tvl=lp_rewardable_tvl, short_rewardable_tvl=short_rewardable_tvl, block=block, - debug=True, ) # get the user's balance rewardable_tvl = sum(position[5] for position in self.pool_positions if position[0] == user) return rewardable_tvl / 1e18 def test_hyperdrive(self): - print(f"=== {HYPERDRIVE_SUSDE_POOL_ADDRESS} ===") - pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) + pool_users, pool_ids = get_hyperdrive_participants( + pool=HYPERDRIVE_SUSDE_POOL_ADDRESS, + start_block=HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, + ) pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) - _, _, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) - print(f"=== {name} ===") + _, _, _, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) pool_positions = get_pool_positions( pool_contract=pool_contract, pool_users=pool_users, pool_ids=pool_ids, lp_rewardable_tvl=lp_rewardable_tvl, short_rewardable_tvl=short_rewardable_tvl, - debug=True, ) - # display stuff total_rewardable = Decimal(sum(position[5] for position in pool_positions)) if vault_shares_balance == total_rewardable: print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") diff --git a/test_hyperdrive.py b/test_hyperdrive.py index 4315052..589e996 100644 --- a/test_hyperdrive.py +++ b/test_hyperdrive.py @@ -1,30 +1,5 @@ # %% -from decimal import Decimal +from integrations.hyperdrive import Hyperdrive -from constants.hyperdrive import HYPERDRIVE_MORPHO_ABI, HYPERDRIVE_SUSDE_POOL_ADDRESS -from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions -from utils.web3_utils import w3 - -## Import -print(f"=== {HYPERDRIVE_SUSDE_POOL_ADDRESS} ===") -pool_users, pool_ids = get_hyperdrive_participants(HYPERDRIVE_SUSDE_POOL_ADDRESS) -pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) -_, _, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) -print(f"=== {name} ===") -pool_positions = get_pool_positions( - pool_contract=pool_contract, - pool_users=pool_users, - pool_ids=pool_ids, - lp_rewardable_tvl=lp_rewardable_tvl, - short_rewardable_tvl=short_rewardable_tvl, - debug=True, -) - -# display stuff -total_rewardable = Decimal(sum(position[5] for position in pool_positions)) -if vault_shares_balance == total_rewardable: - print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") -else: - print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") - -# %% +hyperdrive = Hyperdrive() +hyperdrive.test_hyperdrive() \ No newline at end of file diff --git a/test_hyperdrive2.py b/test_hyperdrive2.py deleted file mode 100644 index 589e996..0000000 --- a/test_hyperdrive2.py +++ /dev/null @@ -1,5 +0,0 @@ -# %% -from integrations.hyperdrive import Hyperdrive - -hyperdrive = Hyperdrive() -hyperdrive.test_hyperdrive() \ No newline at end of file diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index 5a875d7..7bff691 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -29,11 +29,11 @@ def get_first_contract_block(contract_address): assert earliest_block >= latest_block, f"something fucked up since {earliest_block=} isn't greater than or equal to {latest_block=}" return earliest_block -def get_hyperdrive_participants(pool): +def get_hyperdrive_participants(pool, start_block = None): target_block = w3.eth.get_block_number() all_users = set() all_ids = set() - start_block = get_first_contract_block(pool) + start_block = start_block or get_first_contract_block(pool) assert all_users is not None, "error: all_users is None" assert all_ids is not None, "error: all_ids is None" assert start_block is not None, "error: start_block is None" @@ -106,7 +106,7 @@ def get_pool_details(pool_contract): return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl -def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, block = None, debug: bool = False): +def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, block = None): pool_positions = [] combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately bal_by_prefix = {0: Decimal(0), 1: Decimal(0), 2: Decimal(0), 3: Decimal(0)} @@ -116,8 +116,6 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s trade_type, prefix, timestamp = get_trade_details(int(id)) bal = pool_contract.functions.balanceOf(int(id), user).call(block_identifier=block or "latest") if bal > Decimal(1): - if debug: - print(f"user={user[:8]} {trade_type:<4}({prefix=}) {timestamp=:>12} balance={bal:>32}") pool_positions.append([user, trade_type, prefix, timestamp, bal, Decimal(0)]) bal_by_prefix[prefix] += bal # manually hard-code a withdrawal share position @@ -142,19 +140,11 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s for prefixes in combined_prefixes: combined_shares = sum(position[5] for position in pool_positions if position[2] in prefixes) combined_rewardable = lp_rewardable_tvl if prefixes[0] == 0 else short_rewardable_tvl - if debug: - print(f"{prefixes=}") - print(f"{combined_shares=}") - print(f"{combined_rewardable=}") if combined_shares != combined_rewardable: diff = combined_rewardable - combined_shares # Find the position with the largest share among the combined prefixes max_position = max((p for p in pool_positions if p[2] in prefixes), key=lambda x: x[5]) - if debug: - print(f"found {diff=} in {prefixes=}, adjusting\n{max_position=}") max_position[5] += diff - if debug: - print(f"{max_position=}") # Make sure rewards add up to rewardable TVL for prefixes in combined_prefixes: From e1e3795680375dade6d85ff8400ce9fadfa625bc Mon Sep 17 00:00:00 2001 From: Mihai Date: Sun, 20 Oct 2024 02:00:31 -0400 Subject: [PATCH 10/19] move test into test --- integrations/hyperdrive.py | 10 ++++++++++ utils/hyperdrive.py | 13 ------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 66842ec..91636ab 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -69,6 +69,16 @@ def test_hyperdrive(self): short_rewardable_tvl=short_rewardable_tvl, ) + # Make sure rewards add up to rewardable TVL + combined_prefixes = [(0, 3), (2,)] # Treat prefixes 0 and 3 together, 2 separately + for prefixes in combined_prefixes: + combined_shares = sum(position[5] for position in pool_positions if position[2] in prefixes) + combined_rewardable = lp_rewardable_tvl if prefixes[0] == 0 else short_rewardable_tvl + if combined_shares == combined_rewardable: + print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} == {combined_rewardable}) ✅") + else: + print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} != {combined_rewardable}) ❌") + total_rewardable = Decimal(sum(position[5] for position in pool_positions)) if vault_shares_balance == total_rewardable: print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index 7bff691..c90d41b 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -118,10 +118,6 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s if bal > Decimal(1): pool_positions.append([user, trade_type, prefix, timestamp, bal, Decimal(0)]) bal_by_prefix[prefix] += bal - # manually hard-code a withdrawal share position - # bal = 24101344855221864785272839529 - # pool_positions.append(["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "WITHDRAWAL_SHARE", 3, 1678908800, bal, Decimal(0)]) - # bal_by_prefix[3] += bal # Second pass: calculate shares (prefix 1 (longs) get nothing, so we skip it) for position in pool_positions: @@ -146,15 +142,6 @@ def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, s max_position = max((p for p in pool_positions if p[2] in prefixes), key=lambda x: x[5]) max_position[5] += diff - # Make sure rewards add up to rewardable TVL - for prefixes in combined_prefixes: - combined_shares = sum(position[5] for position in pool_positions if position[2] in prefixes) - combined_rewardable = lp_rewardable_tvl if prefixes[0] == 0 else short_rewardable_tvl - if combined_shares == combined_rewardable: - print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} == {combined_rewardable}) ✅") - else: - print(f"for prefixes={prefixes}, check combined_shares == combined_rewardable ({combined_shares} != {combined_rewardable}) ❌") - return pool_positions def get_trade_details(asset_id: int) -> tuple[str, int, int]: From ca4a491b908584d647f47962f4bd096401374772 Mon Sep 17 00:00:00 2001 From: Mihai Date: Mon, 21 Oct 2024 22:33:49 -0400 Subject: [PATCH 11/19] return fewer things --- constants/hyperdrive.py | 4 ---- integrations/hyperdrive.py | 4 ++-- utils/hyperdrive.py | 3 +-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/constants/hyperdrive.py b/constants/hyperdrive.py index 8852a70..efa1f4e 100644 --- a/constants/hyperdrive.py +++ b/constants/hyperdrive.py @@ -8,10 +8,6 @@ with open("abi/IHyperdriveMorpho.json") as f: HYPERDRIVE_MORPHO_ABI = json.load(f) -MORPHO_ABI = None -with open("abi/IMorpho.json") as f: - MORPHO_ABI = json.load(f) - ERC20_ABI = None with open("abi/ERC20_abi.json") as f: ERC20_ABI = json.load(f) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 91636ab..943c876 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -41,7 +41,7 @@ def get_balance(self, user: str, block: int) -> float: self.update_participants() # get pool positions pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) - _, _, _, _, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) + _, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) self.pool_positions = get_pool_positions( pool_contract=pool_contract, pool_users=self.pool_users, @@ -60,7 +60,7 @@ def test_hyperdrive(self): start_block=HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, ) pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) - _, _, _, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) + vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) pool_positions = get_pool_positions( pool_contract=pool_contract, pool_users=pool_users, diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index c90d41b..246113d 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -86,7 +86,6 @@ def decode_asset_id(asset_id: int) -> tuple[int, int]: return prefix, timestamp def get_pool_details(pool_contract): - name = pool_contract.functions.name().call() config_values = pool_contract.functions.getPoolConfig().call() config_outputs = pool_contract.functions.getPoolConfig().abi['outputs'][0]['components'] config_keys = [i['name'] for i in config_outputs if 'name' in i] @@ -104,7 +103,7 @@ def get_pool_details(pool_contract): short_rewardable_tvl = info['shortsOutstanding'] lp_rewardable_tvl = vault_shares_balance - short_rewardable_tvl - return config, info, name, vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl + return vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl def get_pool_positions(pool_contract, pool_users, pool_ids, lp_rewardable_tvl, short_rewardable_tvl, block = None): pool_positions = [] From eb54179f05512d7f533cfb4bde9386e0407aaa43 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 12:55:12 -0400 Subject: [PATCH 12/19] don't cache --- integrations/hyperdrive.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 943c876..ae9df2b 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -30,8 +30,7 @@ def update_participants(self): ) def get_participants(self, blocks: Optional[List[int]]) -> Set[str]: - if self.pool_users is None: - self.update_participants() + self.update_participants() return self.pool_users def get_balance(self, user: str, block: int) -> float: From 00fbc61f42c94b61a75c3cb3fbe0c3066b5ed391 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 12:56:58 -0400 Subject: [PATCH 13/19] fuck parameters and types when inheriting --- integrations/hyperdrive.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index ae9df2b..e280c48 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -1,4 +1,3 @@ -from typing import List, Optional, Set from decimal import Decimal from constants.chains import Chain from models.integration import Integration @@ -29,7 +28,7 @@ def update_participants(self): start_block=HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, ) - def get_participants(self, blocks: Optional[List[int]]) -> Set[str]: + def get_participants(self): self.update_participants() return self.pool_users @@ -82,4 +81,4 @@ def test_hyperdrive(self): if vault_shares_balance == total_rewardable: print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") else: - print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") \ No newline at end of file + print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") From 3c69bf0829a7d1c43f70493ec92e5eaa57849888 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 12:59:30 -0400 Subject: [PATCH 14/19] test more --- integrations/hyperdrive.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index e280c48..16f31f0 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -1,9 +1,10 @@ +import itertools from decimal import Decimal from constants.chains import Chain from models.integration import Integration from constants.integration_ids import IntegrationID from constants.hyperdrive import HYPERDRIVE_SUSDE_POOL_ADDRESS, HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, HYPERDRIVE_MORPHO_ABI -from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions +from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions, get_trade_details from utils.web3_utils import w3 class Hyperdrive(Integration): @@ -53,16 +54,13 @@ def get_balance(self, user: str, block: int) -> float: return rewardable_tvl / 1e18 def test_hyperdrive(self): - pool_users, pool_ids = get_hyperdrive_participants( - pool=HYPERDRIVE_SUSDE_POOL_ADDRESS, - start_block=HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, - ) + self.update_participants() pool_contract = w3.eth.contract(address=w3.to_checksum_address(HYPERDRIVE_SUSDE_POOL_ADDRESS), abi=HYPERDRIVE_MORPHO_ABI) vault_shares_balance, lp_rewardable_tvl, short_rewardable_tvl = get_pool_details(pool_contract) pool_positions = get_pool_positions( pool_contract=pool_contract, - pool_users=pool_users, - pool_ids=pool_ids, + pool_users=self.pool_users, + pool_ids=self.pool_ids, lp_rewardable_tvl=lp_rewardable_tvl, short_rewardable_tvl=short_rewardable_tvl, ) @@ -82,3 +80,15 @@ def test_hyperdrive(self): print(f"vault_shares_balance == total_rewardable ({vault_shares_balance} == {total_rewardable}) ✅") else: print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") + + for user, id in itertools.product(self.pool_users, self.pool_ids): + trade_type, _, _ = get_trade_details(int(id)) + if trade_type == 0: + if pool_contract.functions.balanceOf(int(id), user).call() == Decimal(0): + print(f"balanceOf({id}, {user}) == 0 ({pool_contract.functions.balanceOf(int(id), user).call()} == 0) ✅") + else: + print(f"balanceOf({id}, {user}) != 0 ({pool_contract.functions.balanceOf(int(id), user).call()} != 0) ❌") + +if __name__ == "__main__": + hyperdrive = Hyperdrive() + hyperdrive.test_hyperdrive() From 3b215164e5bccefcb252275accefb096cf05cb0b Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 13:14:26 -0400 Subject: [PATCH 15/19] fewer tests --- integrations/hyperdrive.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 16f31f0..5e32de8 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -81,14 +81,6 @@ def test_hyperdrive(self): else: print(f"vault_shares_balance != total_rewardable ({vault_shares_balance} != {total_rewardable}) ❌") - for user, id in itertools.product(self.pool_users, self.pool_ids): - trade_type, _, _ = get_trade_details(int(id)) - if trade_type == 0: - if pool_contract.functions.balanceOf(int(id), user).call() == Decimal(0): - print(f"balanceOf({id}, {user}) == 0 ({pool_contract.functions.balanceOf(int(id), user).call()} == 0) ✅") - else: - print(f"balanceOf({id}, {user}) != 0 ({pool_contract.functions.balanceOf(int(id), user).call()} != 0) ❌") - if __name__ == "__main__": hyperdrive = Hyperdrive() hyperdrive.test_hyperdrive() From 4dc185a3d36c38dcd8ffc89852c98e7659b1bbb1 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 13:18:04 -0400 Subject: [PATCH 16/19] remove unnecessary imports --- integrations/hyperdrive.py | 3 +-- utils/hyperdrive.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/integrations/hyperdrive.py b/integrations/hyperdrive.py index 5e32de8..268ac26 100644 --- a/integrations/hyperdrive.py +++ b/integrations/hyperdrive.py @@ -1,10 +1,9 @@ -import itertools from decimal import Decimal from constants.chains import Chain from models.integration import Integration from constants.integration_ids import IntegrationID from constants.hyperdrive import HYPERDRIVE_SUSDE_POOL_ADDRESS, HYPERDRIVE_SUSDE_POOL_DEPLOYMENT_BLOCK, HYPERDRIVE_MORPHO_ABI -from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions, get_trade_details +from utils.hyperdrive import get_hyperdrive_participants, get_pool_details, get_pool_positions from utils.web3_utils import w3 class Hyperdrive(Integration): diff --git a/utils/hyperdrive.py b/utils/hyperdrive.py index 246113d..314296d 100644 --- a/utils/hyperdrive.py +++ b/utils/hyperdrive.py @@ -90,6 +90,8 @@ def get_pool_details(pool_contract): config_outputs = pool_contract.functions.getPoolConfig().abi['outputs'][0]['components'] config_keys = [i['name'] for i in config_outputs if 'name' in i] config = dict(zip(config_keys, config_values)) + for k,v in config.items(): + print(f"{k}: {v}") info_values = pool_contract.functions.getPoolInfo().call() info_outputs = pool_contract.functions.getPoolInfo().abi['outputs'][0]['components'] info_keys = [i['name'] for i in info_outputs if 'name' in i] From 7389991af6693243309e8fd9b15d610b59bbc553 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 13:20:02 -0400 Subject: [PATCH 17/19] fix duplicate in Enum was getting this error: ``` Traceback (most recent call last): File "/code/ethena_sats_adapters/integrations/hyperdrive.py", line 3, in from constants.chains import Chain File "/code/ethena_sats_adapters/constants/chains.py", line 4, in class Chain(Enum): File "/code/ethena_sats_adapters/constants/chains.py", line 12, in Chain FRAXTAL = "Fraxtal" File "/usr/lib64/python3.10/enum.py", line 134, in __setitem__ raise TypeError('Attempted to reuse key: %r' % key) TypeError: Attempted to reuse key: 'FRAXTAL' ``` --- constants/chains.py | 1 - 1 file changed, 1 deletion(-) diff --git a/constants/chains.py b/constants/chains.py index f07b4bb..c997b05 100644 --- a/constants/chains.py +++ b/constants/chains.py @@ -9,5 +9,4 @@ class Chain(Enum): BLAST = "Blast" SCROLL = "Scroll" MODE = "Mode" - FRAXTAL = "Fraxtal" OPTIMISM = "Optimism" From 4149e5bf6626d0084fb0180a5a47a6da574f24c5 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 13:20:59 -0400 Subject: [PATCH 18/19] fix duplicate in enum --- constants/chains.py | 1 - 1 file changed, 1 deletion(-) diff --git a/constants/chains.py b/constants/chains.py index f07b4bb..c997b05 100644 --- a/constants/chains.py +++ b/constants/chains.py @@ -9,5 +9,4 @@ class Chain(Enum): BLAST = "Blast" SCROLL = "Scroll" MODE = "Mode" - FRAXTAL = "Fraxtal" OPTIMISM = "Optimism" From a317bbea4565430b9178ccb21852d58f9f1d14b2 Mon Sep 17 00:00:00 2001 From: Mihai Date: Thu, 24 Oct 2024 14:02:28 -0400 Subject: [PATCH 19/19] remove test file --- test_hyperdrive.py | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 test_hyperdrive.py diff --git a/test_hyperdrive.py b/test_hyperdrive.py deleted file mode 100644 index 589e996..0000000 --- a/test_hyperdrive.py +++ /dev/null @@ -1,5 +0,0 @@ -# %% -from integrations.hyperdrive import Hyperdrive - -hyperdrive = Hyperdrive() -hyperdrive.test_hyperdrive() \ No newline at end of file