From 832b2d7c53b649b826f135a6b52e20ea440dee6c Mon Sep 17 00:00:00 2001 From: cyc60 Date: Fri, 29 Nov 2024 18:34:12 +0300 Subject: [PATCH 01/19] Implement osToken force exit and claim --- src/common/networks.py | 20 +- src/exit/__init__.py | 0 src/exit/abi/ILeverageStrategy.json | 911 ++++++++++++++++++++++++++ src/exit/abi/IOsTokenVaultEscrow.json | 489 ++++++++++++++ src/exit/clients.py | 25 + src/exit/contracts.py | 60 ++ src/exit/graph.py | 75 +++ src/exit/settings.py | 5 + src/exit/tasks.py | 63 ++ src/exit/typings.py | 18 + src/force_exit.py | 12 + 11 files changed, 1676 insertions(+), 2 deletions(-) create mode 100644 src/exit/__init__.py create mode 100644 src/exit/abi/ILeverageStrategy.json create mode 100644 src/exit/abi/IOsTokenVaultEscrow.json create mode 100644 src/exit/clients.py create mode 100644 src/exit/contracts.py create mode 100644 src/exit/graph.py create mode 100644 src/exit/settings.py create mode 100644 src/exit/tasks.py create mode 100644 src/exit/typings.py create mode 100644 src/force_exit.py diff --git a/src/common/networks.py b/src/common/networks.py index 6b1f0fc..45ae224 100644 --- a/src/common/networks.py +++ b/src/common/networks.py @@ -1,10 +1,10 @@ from dataclasses import dataclass from enum import Enum -from eth_typing import ChecksumAddress, HexAddress, HexStr +from ens.constants import EMPTY_ADDR_HEX +from eth_typing import ChecksumAddress from web3 import Web3 -EMPTY_ADDR_HEX = HexAddress(HexStr('0x' + '00' * 20)) ZERO_CHECKSUM_ADDRESS = Web3.to_checksum_address(EMPTY_ADDR_HEX) # noqa @@ -32,6 +32,8 @@ class PriceNetworkConfig: @dataclass class NetworkConfig: VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS: ChecksumAddress + LEVERAGE_STRATEGY_CONTRACT_ADDRESS: ChecksumAddress + VAULT_ESCROW_CONTRACT_ADDRESS: ChecksumAddress PRICE_NETWORK_CONFIG: PriceNetworkConfig | None = None @@ -40,6 +42,8 @@ class NetworkConfig: VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, PRICE_NETWORK_CONFIG=( PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. @@ -64,19 +68,31 @@ class NetworkConfig: VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0x8f48130b9b96B58035b4A9389eCDaBC00d59d0c8' ), + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0xdB38cfc6e98a34Cdc60c568f607417E646C75B34' + ), + VAULT_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0x81Ab00dD782492D62105B8fa9B03E82d4B57798C' + ), ), Network.GNOSIS: NetworkConfig( VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xdEa72c54f63470349CE2dC12f8232FE00241abE6' ), + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), Network.CHIADO: NetworkConfig( VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), Network.SEPOLIA: NetworkConfig( VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, PRICE_NETWORK_CONFIG=( PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. diff --git a/src/exit/__init__.py b/src/exit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/exit/abi/ILeverageStrategy.json b/src/exit/abi/ILeverageStrategy.json new file mode 100644 index 0000000..db44cda --- /dev/null +++ b/src/exit/abi/ILeverageStrategy.json @@ -0,0 +1,911 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "osToken", + "type": "address" + }, + { + "internalType": "address", + "name": "assetToken", + "type": "address" + }, + { + "internalType": "address", + "name": "osTokenVaultController", + "type": "address" + }, + { + "internalType": "address", + "name": "osTokenConfig", + "type": "address" + }, + { + "internalType": "address", + "name": "osTokenFlashLoans", + "type": "address" + }, + { + "internalType": "address", + "name": "osTokenVaultEscrow", + "type": "address" + }, + { + "internalType": "address", + "name": "strategiesRegistry", + "type": "address" + }, + { + "internalType": "address", + "name": "strategyProxyImplementation", + "type": "address" + }, + { + "internalType": "address", + "name": "balancerVault", + "type": "address" + }, + { + "internalType": "address", + "name": "aavePool", + "type": "address" + }, + { + "internalType": "address", + "name": "aaveOsToken", + "type": "address" + }, + { + "internalType": "address", + "name": "aaveVarDebtAssetToken", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessDenied", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ERC1167FailedCreateClone", + "type": "error" + }, + { + "inputs": [], + "name": "ExitQueueNotEntered", + "type": "error" + }, + { + "inputs": [], + "name": "ExitRequestNotProcessed", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAssets", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBalancerPoolId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidExitQueuePercent", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidExitQueueTicket", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidFlashloanAction", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMaxSlippagePercent", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPosition", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShares", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidVault", + "type": "error" + }, + { + "inputs": [], + "name": "MathOverflowedMulDiv", + "type": "error" + }, + { + "inputs": [], + "name": "NotHarvested", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [], + "name": "UpgradeFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ValueNotChanged", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "leverageOsTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "positionPercent", + "type": "uint256" + } + ], + "name": "ExitQueueEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "ExitedAssetsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "LendingAssetsRescued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "StrategyProxyCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "strategy", + "type": "address" + } + ], + "name": "StrategyProxyUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "VaultAssetsRescued", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "canForceEnterExitQueue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitQueueIndex", + "type": "uint256" + } + ], + "internalType": "struct ILeverageStrategy.ExitPosition", + "name": "exitPosition", + "type": "tuple" + } + ], + "name": "claimExitedAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "positionPercent", + "type": "uint256" + } + ], + "name": "enterExitQueue", + "outputs": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "forceEnterExitQueue", + "outputs": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBorrowLtv", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "getBorrowState", + "outputs": [ + { + "internalType": "uint256", + "name": "borrowedAssets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "suppliedOsTokenShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + } + ], + "name": "getFlashloanOsTokenShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getStrategyProxy", + "outputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "getVaultLtv", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "getVaultState", + "outputs": [ + { + "internalType": "uint256", + "name": "stakedAssets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintedOsTokenShares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "isStrategyProxyExiting", + "outputs": [ + { + "internalType": "bool", + "name": "isExiting", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "receiveFlashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippagePercent", + "type": "uint256" + } + ], + "name": "rescueLendingAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitQueueIndex", + "type": "uint256" + } + ], + "internalType": "struct ILeverageStrategy.ExitPosition", + "name": "exitPosition", + "type": "tuple" + } + ], + "name": "rescueVaultAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "strategyId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "int160", + "name": "reward", + "type": "int160" + }, + { + "internalType": "uint160", + "name": "unlockedMevReward", + "type": "uint160" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "internalType": "struct IKeeperRewards.HarvestParams", + "name": "harvestParams", + "type": "tuple" + } + ], + "name": "updateVaultState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "upgradeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/exit/abi/IOsTokenVaultEscrow.json b/src/exit/abi/IOsTokenVaultEscrow.json new file mode 100644 index 0000000..f0b68f1 --- /dev/null +++ b/src/exit/abi/IOsTokenVaultEscrow.json @@ -0,0 +1,489 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newAuthenticator", + "type": "address" + } + ], + "name": "AuthenticatorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "ExitedAssetsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exitedAssets", + "type": "uint256" + } + ], + "name": "ExitedAssetsProcessed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "liqThresholdPercent", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liqBonusPercent", + "type": "uint256" + } + ], + "name": "LiqConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "receivedAssets", + "type": "uint256" + } + ], + "name": "OsTokenLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "receivedAssets", + "type": "uint256" + } + ], + "name": "OsTokenRedeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeFeePerShare", + "type": "uint256" + } + ], + "name": "PositionCreated", + "type": "event" + }, + { + "inputs": [], + "name": "authenticator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + } + ], + "name": "claimExitedAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "claimedAssets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + } + ], + "name": "getPosition", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liqBonusPercent", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liqThresholdPercent", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "liquidateOsToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitQueueIndex", + "type": "uint256" + } + ], + "name": "processExitedAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "redeemOsToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exitPositionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeFeePerShare", + "type": "uint256" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAuthenticator", + "type": "address" + } + ], + "name": "setAuthenticator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "_liqThresholdPercent", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "_liqBonusPercent", + "type": "uint256" + } + ], + "name": "updateLiqConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/exit/clients.py b/src/exit/clients.py new file mode 100644 index 0000000..082e72f --- /dev/null +++ b/src/exit/clients.py @@ -0,0 +1,25 @@ +import logging + +import gql +from gql.transport.requests import RequestsHTTPTransport + +from src.common.clients import get_execution_client, hot_wallet_account +from src.common.settings import EXECUTION_ENDPOINT + +from .settings import GRAPH_API_TIMEOUT, GRAPH_API_URL + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +execution_client = get_execution_client(EXECUTION_ENDPOINT, account=hot_wallet_account) + + +def get_graph_client() -> gql.Client: + transport = RequestsHTTPTransport( + url=GRAPH_API_URL, + timeout=GRAPH_API_TIMEOUT, + ) + return gql.Client(transport=transport) + + +graph_client = get_graph_client() diff --git a/src/exit/contracts.py b/src/exit/contracts.py new file mode 100644 index 0000000..563f16b --- /dev/null +++ b/src/exit/contracts.py @@ -0,0 +1,60 @@ +import logging + +from eth_typing import ChecksumAddress +from hexbytes import HexBytes +from web3.types import Wei + +from src.common.clients import hot_wallet_account +from src.common.contracts import ContractWrapper +from src.common.settings import network_config + +from .clients import execution_client + +logger = logging.getLogger(__name__) + +ABI_DIR = 'src/exit/abi' + + +class LeverageStrategyContract(ContractWrapper): + def can_force_enter_exit_queue(self, vault: ChecksumAddress, user: ChecksumAddress) -> bool: + return self.contract.functions.canForceEnterExitQueue(vault, user).call() + + def force_enter_exit_queue( + self, + vault: ChecksumAddress, + user: ChecksumAddress, + ) -> HexBytes: + return self.contract.functions.forceEnterExitQueue( + vault, + user, + ).transact({'from': hot_wallet_account.address}) + + +class OsTokenVaultEscrowContract(ContractWrapper): + def liq_threshold_percent(self) -> int: + return self.contract.functions.liqThresholdPercent().call() + + def claim_exited_assets( + self, + vault: ChecksumAddress, + exit_position_ticket: int, + os_token_shares: Wei, + ) -> HexBytes: + return self.contract.functions.claimExitedAssets( + vault, + exit_position_ticket, + os_token_shares, + ).transact({'from': hot_wallet_account.address}) + + +leverage_strategy_contract = LeverageStrategyContract( + abi_path=f'{ABI_DIR}/ILeverageStrategy.json', + address=network_config.LEVERAGE_STRATEGY_CONTRACT_ADDRESS, + client=execution_client, +) + +ostoken_vault_escrow_contract = OsTokenVaultEscrowContract( + abi_path=f'{ABI_DIR}/IOsTokenVaultEscrow.json', + address=network_config.VAULT_ESCROW_CONTRACT_ADDRESS, + client=execution_client, +) diff --git a/src/exit/graph.py b/src/exit/graph.py new file mode 100644 index 0000000..621ec4b --- /dev/null +++ b/src/exit/graph.py @@ -0,0 +1,75 @@ +import logging + +from gql import gql +from web3 import Web3 +from web3.types import Wei + +from .clients import graph_client +from .typings import LeveragePosition, OsTokenExitRequest + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def graph_get_leverage_positions() -> list[LeveragePosition]: + query = gql( + """ + query PositionsQuery { + leverageStrategyPositions { + user + osTokenShares + id + proxy + vault { + id + } + } + } + """ + ) + + response = graph_client.execute(query) + result = [] + for data in response['leverageStrategyPositions']: # pylint: disable=unsubscriptable-object + result.append( + LeveragePosition( + vault=Web3.to_checksum_address(data['vault']['id']), + user=Web3.to_checksum_address(data['user']), + ) + ) + return result + + +def graph_osToken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: + query = gql( + """ + query ExitRequestsQuery($ltv: String) { + osTokenExitRequests(where: {ltv_gt: $ltv}) { + id + owner + ltv + positionTicket + osTokenShares + vault { + id + } + } + } + """ + ) + params = { + 'ltv': ltv, + } + response = graph_client.execute(query, params) + result = [] + for data in response['osTokenExitRequests']: # pylint: disable=unsubscriptable-object + result.append( + OsTokenExitRequest( + vault=Web3.to_checksum_address(data['vault']['id']), + owner=Web3.to_checksum_address(data['owner']), + os_token_shares=Wei(int(data['osTokenShares'])), + ltv=data['ltv'], + position_ticket=int(data['positionTicket']), + ) + ) + return result diff --git a/src/exit/settings.py b/src/exit/settings.py new file mode 100644 index 0000000..b558027 --- /dev/null +++ b/src/exit/settings.py @@ -0,0 +1,5 @@ +from decouple import config + +# graph +GRAPH_API_URL: str = config('GRAPH_API_URL') +GRAPH_API_TIMEOUT: int = config('GRAPH_API_TIMEOUT', default='10', cast=int) diff --git a/src/exit/tasks.py b/src/exit/tasks.py new file mode 100644 index 0000000..5f2a039 --- /dev/null +++ b/src/exit/tasks.py @@ -0,0 +1,63 @@ +import logging + +from .clients import execution_client +from .contracts import leverage_strategy_contract, ostoken_vault_escrow_contract +from .graph import graph_get_leverage_positions, graph_osToken_exit_requests + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def force_exits() -> None: + """Force handle ltv overflow. Trigger os token position exits""" + # Get max LTV user for vault + block = execution_client.eth.get_block('finalized') + logger.debug('Current block: %d', block['number']) + + # force exit leverage positions + leverage_positions = graph_get_leverage_positions() + logger.info('Checking %d leverage positions...', len(leverage_positions)) + + for position in leverage_positions: + if leverage_strategy_contract.can_force_enter_exit_queue( + vault=position.vault, + user=position.user, + ): + logger.info( + 'Force exiting leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + leverage_strategy_contract.force_enter_exit_queue( + vault=position.vault, + user=position.user, + ) + logger.info( + 'Successfully exited leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + + # force claim for exit positions + max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() + exit_requests = graph_osToken_exit_requests(str(max_ltv_percent / 10**18)) + logger.info('Force assets claim for %d exit requests...', len(exit_requests)) + + for exit_request in exit_requests: + logger.info( + 'Claiming exited assets: vault=%s, user=%s...', + exit_request.vault, + exit_request.owner, + ) + ostoken_vault_escrow_contract.claim_exited_assets( + vault=exit_request.vault, + exit_position_ticket=exit_request.position_ticket, + os_token_shares=exit_request.os_token_shares, + ) + logger.info( + 'Successfully claimed exited assets: vault=%s, user=%s...', + exit_request.vault, + exit_request.owner, + ) + + logger.info('Completed') diff --git a/src/exit/typings.py b/src/exit/typings.py new file mode 100644 index 0000000..ced616b --- /dev/null +++ b/src/exit/typings.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from web3.types import ChecksumAddress, Wei + + +@dataclass +class LeveragePosition: + user: ChecksumAddress + vault: ChecksumAddress + + +@dataclass +class OsTokenExitRequest: + vault: ChecksumAddress + owner: ChecksumAddress + position_ticket: int + os_token_shares: Wei + ltv: int diff --git a/src/force_exit.py b/src/force_exit.py new file mode 100644 index 0000000..9370936 --- /dev/null +++ b/src/force_exit.py @@ -0,0 +1,12 @@ +import logging +import sys + +from src.exit.tasks import force_exits + +logger = logging.getLogger(__name__) + +try: + force_exits() +except Exception as e: + logger.exception(e) + sys.exit(1) From 4f29495f3266f08f1946b0da743c2b5930271db0 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Tue, 3 Dec 2024 12:14:23 +0300 Subject: [PATCH 02/19] Order exits by ltv --- src/common/logs.py | 8 ++++++++ src/exit/graph.py | 35 +++++++++++++++++++++++++++++++---- src/exit/tasks.py | 41 ++++++++++++++++++++++++++++++++++++++++- src/exit/typings.py | 1 + src/force_exit.py | 2 ++ src/update_ltv.py | 2 ++ 6 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/common/logs.py diff --git a/src/common/logs.py b/src/common/logs.py new file mode 100644 index 0000000..68b5fc9 --- /dev/null +++ b/src/common/logs.py @@ -0,0 +1,8 @@ +import logging + +from gql.transport.requests import log as requests_logger + + +def setup_gql_log_level() -> None: + """Raise GQL default log level""" + requests_logger.setLevel(logging.WARNING) diff --git a/src/exit/graph.py b/src/exit/graph.py index 621ec4b..9be74f7 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -2,7 +2,7 @@ from gql import gql from web3 import Web3 -from web3.types import Wei +from web3.types import ChecksumAddress, Wei from .clients import graph_client from .typings import LeveragePosition, OsTokenExitRequest @@ -15,10 +15,10 @@ def graph_get_leverage_positions() -> list[LeveragePosition]: query = gql( """ query PositionsQuery { - leverageStrategyPositions { + leverageStrategyPositions( + orderBy: borrowLtv, orderDirection: desc + ) { user - osTokenShares - id proxy vault { id @@ -35,11 +35,38 @@ def graph_get_leverage_positions() -> list[LeveragePosition]: LeveragePosition( vault=Web3.to_checksum_address(data['vault']['id']), user=Web3.to_checksum_address(data['user']), + proxy=Web3.to_checksum_address(data['proxy']), ) ) return result +def graph_get_allocators(addresses: list[ChecksumAddress]) -> list[ChecksumAddress]: + query = gql( + """ + query AllocatorsQuery($addresses: [String]) { + allocators( + where: {address_in: $addresses}, + orderBy: ltv, + orderDirection: desc + ) { + address + } + } + """ + ) + params = { + 'addresses': [address.lower() for address in addresses], + } + response = graph_client.execute(query, params) + result = [] + for data in response['allocators']: # pylint: disable=unsubscriptable-object + result.append( + Web3.to_checksum_address(data['address']), + ) + return result + + def graph_osToken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: query = gql( """ diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 5f2a039..f44df59 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -2,7 +2,11 @@ from .clients import execution_client from .contracts import leverage_strategy_contract, ostoken_vault_escrow_contract -from .graph import graph_get_leverage_positions, graph_osToken_exit_requests +from .graph import ( + graph_get_allocators, + graph_get_leverage_positions, + graph_osToken_exit_requests, +) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -18,6 +22,7 @@ def force_exits() -> None: leverage_positions = graph_get_leverage_positions() logger.info('Checking %d leverage positions...', len(leverage_positions)) + # check by position borrow ltv for position in leverage_positions: if leverage_strategy_contract.can_force_enter_exit_queue( vault=position.vault, @@ -37,6 +42,40 @@ def force_exits() -> None: position.vault, position.user, ) + else: + # position sorted by ltv and next will have lower ltv + break + + # check by position proxy ltv + # addresses = [position.proxy for position in leverage_positions] + proxy_to_position = {position.proxy: position for position in leverage_positions} + allocators = graph_get_allocators(list(proxy_to_position.keys())) + leverage_positions_by_ltv = [] + for allocator in allocators: + leverage_positions_by_ltv.append(proxy_to_position[allocator]) + + for position in leverage_positions_by_ltv: + if leverage_strategy_contract.can_force_enter_exit_queue( + vault=position.vault, + user=position.user, + ): + logger.info( + 'Force exiting leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + leverage_strategy_contract.force_enter_exit_queue( + vault=position.vault, + user=position.user, + ) + logger.info( + 'Successfully exited leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + else: + # position sorted by ltv and next will have lower ltv + break # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() diff --git a/src/exit/typings.py b/src/exit/typings.py index ced616b..44c69c4 100644 --- a/src/exit/typings.py +++ b/src/exit/typings.py @@ -7,6 +7,7 @@ class LeveragePosition: user: ChecksumAddress vault: ChecksumAddress + proxy: ChecksumAddress @dataclass diff --git a/src/force_exit.py b/src/force_exit.py index 9370936..3ef5259 100644 --- a/src/force_exit.py +++ b/src/force_exit.py @@ -1,11 +1,13 @@ import logging import sys +from src.common.logs import setup_gql_log_level from src.exit.tasks import force_exits logger = logging.getLogger(__name__) try: + setup_gql_log_level() force_exits() except Exception as e: logger.exception(e) diff --git a/src/update_ltv.py b/src/update_ltv.py index cc771e2..d190e31 100644 --- a/src/update_ltv.py +++ b/src/update_ltv.py @@ -1,11 +1,13 @@ import logging import sys +from src.common.logs import setup_gql_log_level from src.ltv.tasks import update_vault_max_ltv_user logger = logging.getLogger(__name__) try: + setup_gql_log_level() update_vault_max_ltv_user() except Exception as e: logger.error(e) From 7bca07a33cc4bbd7bd62e3b5860f508bbe362006 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Tue, 3 Dec 2024 12:17:51 +0300 Subject: [PATCH 03/19] Version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 06a08db..d13567e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "periodic-tasks" -version = "v0.1.0" +version = "v0.2.0" description = "" authors = ["StakeWise Labs "] readme = "README.md" From 73243775041b90c12247675afcf59b95e2e45fa8 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Tue, 3 Dec 2024 12:34:08 +0300 Subject: [PATCH 04/19] Little cleanup --- src/exit/graph.py | 8 +------- src/exit/tasks.py | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/exit/graph.py b/src/exit/graph.py index 9be74f7..c05e32f 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -1,5 +1,3 @@ -import logging - from gql import gql from web3 import Web3 from web3.types import ChecksumAddress, Wei @@ -7,9 +5,6 @@ from .clients import graph_client from .typings import LeveragePosition, OsTokenExitRequest -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - def graph_get_leverage_positions() -> list[LeveragePosition]: query = gql( @@ -67,12 +62,11 @@ def graph_get_allocators(addresses: list[ChecksumAddress]) -> list[ChecksumAddre return result -def graph_osToken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: +def graph_ostoken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: query = gql( """ query ExitRequestsQuery($ltv: String) { osTokenExitRequests(where: {ltv_gt: $ltv}) { - id owner ltv positionTicket diff --git a/src/exit/tasks.py b/src/exit/tasks.py index f44df59..05ec5d4 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -5,7 +5,7 @@ from .graph import ( graph_get_allocators, graph_get_leverage_positions, - graph_osToken_exit_requests, + graph_ostoken_exit_requests, ) logging.basicConfig(level=logging.INFO) @@ -14,7 +14,6 @@ def force_exits() -> None: """Force handle ltv overflow. Trigger os token position exits""" - # Get max LTV user for vault block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) @@ -79,7 +78,7 @@ def force_exits() -> None: # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() - exit_requests = graph_osToken_exit_requests(str(max_ltv_percent / 10**18)) + exit_requests = graph_ostoken_exit_requests(str(max_ltv_percent / 10**18)) logger.info('Force assets claim for %d exit requests...', len(exit_requests)) for exit_request in exit_requests: From c0361640903f3d4e5957da04577b87b8ba86b063 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 5 Dec 2024 01:35:01 +0300 Subject: [PATCH 05/19] Review fixes --- pyproject.toml | 3 +++ src/common/clients.py | 1 + src/common/networks.py | 20 +++++++++------ src/common/settings.py | 4 +++ src/exit/contracts.py | 7 +++--- src/exit/graph.py | 38 +++++++++++++++------------- src/exit/tasks.py | 56 ++++++++++++++++++++++++++++++++---------- src/ltv/contracts.py | 3 +-- 8 files changed, 88 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d13567e..60f6b00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,3 +83,6 @@ exclude = ''' [tool.vulture] exclude = ["networks.py"] +ignore_names = [ + "default_account", # execution client +] diff --git a/src/common/clients.py b/src/common/clients.py index 5c707d2..192d52b 100644 --- a/src/common/clients.py +++ b/src/common/clients.py @@ -15,6 +15,7 @@ def get_execution_client(endpoint: str, account: LocalAccount | None = None) -> client = Web3(Web3.HTTPProvider(endpoint)) if account: client.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) + client.eth.default_account = account.address return client diff --git a/src/common/networks.py b/src/common/networks.py index 45ae224..65f0743 100644 --- a/src/common/networks.py +++ b/src/common/networks.py @@ -33,7 +33,7 @@ class PriceNetworkConfig: class NetworkConfig: VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS: ChecksumAddress LEVERAGE_STRATEGY_CONTRACT_ADDRESS: ChecksumAddress - VAULT_ESCROW_CONTRACT_ADDRESS: ChecksumAddress + OSTOKEN_ESCROW_CONTRACT_ADDRESS: ChecksumAddress PRICE_NETWORK_CONFIG: PriceNetworkConfig | None = None @@ -42,8 +42,12 @@ class NetworkConfig: VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), - LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + LEVERAGE_STRATEGY_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0x48cD14FDB8e72A03C8D952af081DBB127D6281fc' + ), + OSTOKEN_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0x09e84205DF7c68907e619D07aFD90143c5763605' + ), PRICE_NETWORK_CONFIG=( PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. @@ -71,8 +75,8 @@ class NetworkConfig: LEVERAGE_STRATEGY_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xdB38cfc6e98a34Cdc60c568f607417E646C75B34' ), - VAULT_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( - '0x81Ab00dD782492D62105B8fa9B03E82d4B57798C' + OSTOKEN_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0x807305c086A99cbDBff07cB4256cE556d9d6F0af' ), ), Network.GNOSIS: NetworkConfig( @@ -80,19 +84,19 @@ class NetworkConfig: '0xdEa72c54f63470349CE2dC12f8232FE00241abE6' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), Network.CHIADO: NetworkConfig( VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), Network.SEPOLIA: NetworkConfig( VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - VAULT_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, PRICE_NETWORK_CONFIG=( PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. diff --git a/src/common/settings.py b/src/common/settings.py index efbcd2c..1db6e18 100644 --- a/src/common/settings.py +++ b/src/common/settings.py @@ -5,5 +5,9 @@ EXECUTION_ENDPOINT: str = config('EXECUTION_ENDPOINT', default='') HOT_WALLET_PRIVATE_KEY: str = config('HOT_WALLET_PRIVATE_KEY') + +EXECUTION_TRANSACTION_TIMEOUT: int = config('EXECUTION_TRANSACTION_TIMEOUT', default=300, cast=int) + + NETWORK: Network = config('NETWORK', cast=Network) network_config = NETWORKS[NETWORK] diff --git a/src/exit/contracts.py b/src/exit/contracts.py index 563f16b..8408705 100644 --- a/src/exit/contracts.py +++ b/src/exit/contracts.py @@ -4,7 +4,6 @@ from hexbytes import HexBytes from web3.types import Wei -from src.common.clients import hot_wallet_account from src.common.contracts import ContractWrapper from src.common.settings import network_config @@ -27,7 +26,7 @@ def force_enter_exit_queue( return self.contract.functions.forceEnterExitQueue( vault, user, - ).transact({'from': hot_wallet_account.address}) + ).transact() class OsTokenVaultEscrowContract(ContractWrapper): @@ -44,7 +43,7 @@ def claim_exited_assets( vault, exit_position_ticket, os_token_shares, - ).transact({'from': hot_wallet_account.address}) + ).transact() leverage_strategy_contract = LeverageStrategyContract( @@ -55,6 +54,6 @@ def claim_exited_assets( ostoken_vault_escrow_contract = OsTokenVaultEscrowContract( abi_path=f'{ABI_DIR}/IOsTokenVaultEscrow.json', - address=network_config.VAULT_ESCROW_CONTRACT_ADDRESS, + address=network_config.OSTOKEN_ESCROW_CONTRACT_ADDRESS, client=execution_client, ) diff --git a/src/exit/graph.py b/src/exit/graph.py index c05e32f..6290734 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -1,17 +1,19 @@ from gql import gql from web3 import Web3 -from web3.types import ChecksumAddress, Wei +from web3.types import BlockNumber, ChecksumAddress, Wei from .clients import graph_client from .typings import LeveragePosition, OsTokenExitRequest -def graph_get_leverage_positions() -> list[LeveragePosition]: +def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosition]: query = gql( """ - query PositionsQuery { + query PositionsQuery($block: BigInt) { leverageStrategyPositions( - orderBy: borrowLtv, orderDirection: desc + block: { number: $block }, + orderBy: borrowLtv, + orderDirection: desc ) { user proxy @@ -22,8 +24,8 @@ def graph_get_leverage_positions() -> list[LeveragePosition]: } """ ) - - response = graph_client.execute(query) + params = {'block': block_number} + response = graph_client.execute(query, params) result = [] for data in response['leverageStrategyPositions']: # pylint: disable=unsubscriptable-object result.append( @@ -36,11 +38,14 @@ def graph_get_leverage_positions() -> list[LeveragePosition]: return result -def graph_get_allocators(addresses: list[ChecksumAddress]) -> list[ChecksumAddress]: +def graph_get_allocators( + addresses: list[ChecksumAddress], block_number: BlockNumber +) -> list[ChecksumAddress]: query = gql( """ - query AllocatorsQuery($addresses: [String]) { + query AllocatorsQuery($addresses: [String], $block: BigInt) { allocators( + block: { number: $block }, where: {address_in: $addresses}, orderBy: ltv, orderDirection: desc @@ -50,9 +55,7 @@ def graph_get_allocators(addresses: list[ChecksumAddress]) -> list[ChecksumAddre } """ ) - params = { - 'addresses': [address.lower() for address in addresses], - } + params = {'addresses': [address.lower() for address in addresses], 'block': block_number} response = graph_client.execute(query, params) result = [] for data in response['allocators']: # pylint: disable=unsubscriptable-object @@ -62,11 +65,14 @@ def graph_get_allocators(addresses: list[ChecksumAddress]) -> list[ChecksumAddre return result -def graph_ostoken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: +def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsTokenExitRequest]: query = gql( """ - query ExitRequestsQuery($ltv: String) { - osTokenExitRequests(where: {ltv_gt: $ltv}) { + query ExitRequestsQuery($ltv: String, $block: BigInt) { + osTokenExitRequests( + block: { number: $block }, + where: {ltv_gt: $ltv} + ) { owner ltv positionTicket @@ -78,9 +84,7 @@ def graph_ostoken_exit_requests(ltv: str) -> list[OsTokenExitRequest]: } """ ) - params = { - 'ltv': ltv, - } + params = {'ltv': str(ltv), 'block': block_number} response = graph_client.execute(query, params) result = [] for data in response['osTokenExitRequests']: # pylint: disable=unsubscriptable-object diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 05ec5d4..39ec26e 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -1,5 +1,10 @@ import logging +from eth_typing import ChecksumAddress, HexStr +from web3 import Web3 + +from src.common.settings import EXECUTION_TRANSACTION_TIMEOUT + from .clients import execution_client from .contracts import leverage_strategy_contract, ostoken_vault_escrow_contract from .graph import ( @@ -13,12 +18,15 @@ def force_exits() -> None: - """Force handle ltv overflow. Trigger os token position exits""" + """ + Monitor leverage positions and trigger exits/claims for those + that approach the liquidation threshold. + """ block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) - + block_number = block['number'] # force exit leverage positions - leverage_positions = graph_get_leverage_positions() + leverage_positions = graph_get_leverage_positions(block_number=block_number) logger.info('Checking %d leverage positions...', len(leverage_positions)) # check by position borrow ltv @@ -32,23 +40,23 @@ def force_exits() -> None: position.vault, position.user, ) - leverage_strategy_contract.force_enter_exit_queue( + tx_hash = _force_enter_exit_queue( vault=position.vault, user=position.user, ) - logger.info( - 'Successfully exited leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) + if tx_hash: + logger.info( + 'Successfully triggered exit for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) else: # position sorted by ltv and next will have lower ltv break # check by position proxy ltv - # addresses = [position.proxy for position in leverage_positions] proxy_to_position = {position.proxy: position for position in leverage_positions} - allocators = graph_get_allocators(list(proxy_to_position.keys())) + allocators = graph_get_allocators(list(proxy_to_position.keys()), block_number=block_number) leverage_positions_by_ltv = [] for allocator in allocators: leverage_positions_by_ltv.append(proxy_to_position[allocator]) @@ -78,7 +86,8 @@ def force_exits() -> None: # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() - exit_requests = graph_ostoken_exit_requests(str(max_ltv_percent / 10**18)) + max_ltv_percent = max_ltv_percent // 10**18 * 100 + exit_requests = graph_ostoken_exit_requests(max_ltv_percent, block_number=block_number) logger.info('Force assets claim for %d exit requests...', len(exit_requests)) for exit_request in exit_requests: @@ -98,4 +107,25 @@ def force_exits() -> None: exit_request.owner, ) - logger.info('Completed') + +def _force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: + try: + tx = leverage_strategy_contract.force_enter_exit_queue( + vault=vault, + user=user, + ) + except Exception as e: + logger.error('Failed to force enter exit queue; vault=%s, user=%s %s: ', vault, user, e) + logger.exception(e) + return None + + tx_hash = Web3.to_hex(tx) + logger.info('Waiting for transaction %s confirmation', tx_hash) + tx_receipt = execution_client.eth.wait_for_transaction_receipt( + tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + ) + if not tx_receipt['status']: + logger.error('Force enter exit queue transaction failed') + return None + + return tx_hash diff --git a/src/ltv/contracts.py b/src/ltv/contracts.py index b447d3a..30076ea 100644 --- a/src/ltv/contracts.py +++ b/src/ltv/contracts.py @@ -5,7 +5,6 @@ from web3 import Web3 from web3.types import Wei -from src.common.clients import hot_wallet_account from src.common.contracts import ContractWrapper from src.common.settings import network_config @@ -55,7 +54,7 @@ def update_vault_max_ltv_user( harvest_params.unlocked_mev_reward, harvest_params.proof, ), - ).transact({'from': hot_wallet_account.address}) + ).transact() @staticmethod def _get_zero_harvest_params() -> HarvestParams: From e748489c6411d8f581fb99e3a73eccbf937c966a Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 5 Dec 2024 18:51:20 +0300 Subject: [PATCH 06/19] Add multicall for can exit checks --- poetry.lock | 639 ++++++------- pyproject.toml | 2 +- src/common/contracts.py | 15 +- src/common/networks.py | 115 ++- src/common/settings.py | 5 +- src/{ltv => common}/typings.py | 0 src/exit/abi/IEthVault.json | 1288 +++++++++++++++++++++++++++ src/exit/abi/IKeeper.json | 737 +++++++++++++++ src/exit/abi/IStrategyRegistry.json | 378 ++++++++ src/exit/abi/Multicall.json | 440 +++++++++ src/exit/contracts.py | 66 +- src/exit/graph.py | 21 +- src/exit/tasks.py | 94 +- src/ltv/contracts.py | 9 +- src/ltv/graph.py | 3 +- src/price/contracts.py | 2 +- src/price/settings.py | 7 - src/price/tasks.py | 2 +- 18 files changed, 3415 insertions(+), 408 deletions(-) rename src/{ltv => common}/typings.py (100%) create mode 100644 src/exit/abi/IEthVault.json create mode 100644 src/exit/abi/IKeeper.json create mode 100644 src/exit/abi/IStrategyRegistry.json create mode 100644 src/exit/abi/Multicall.json diff --git a/poetry.lock b/poetry.lock index 179689e..feb809a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiohappyeyeballs" -version = "2.4.3" +version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, + {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, + {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, ] [[package]] @@ -1296,13 +1296,13 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= [[package]] name = "identify" -version = "2.6.2" +version = "2.6.3" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ - {file = "identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3"}, - {file = "identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd"}, + {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, + {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, ] [package.extras] @@ -1815,129 +1815,113 @@ virtualenv = ">=20.10.0" [[package]] name = "propcache" -version = "0.2.0" +version = "0.2.1" description = "Accelerated property cache" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, + {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, + {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, + {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, + {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, + {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, + {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, + {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, + {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, + {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, + {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, + {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, + {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, ] [[package]] name = "protobuf" -version = "5.28.3" +version = "5.29.1" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, - {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, - {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, - {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, - {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, - {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, - {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, - {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, - {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, + {file = "protobuf-5.29.1-cp310-abi3-win32.whl", hash = "sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110"}, + {file = "protobuf-5.29.1-cp310-abi3-win_amd64.whl", hash = "sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34"}, + {file = "protobuf-5.29.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18"}, + {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155"}, + {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d"}, + {file = "protobuf-5.29.1-cp38-cp38-win32.whl", hash = "sha256:50879eb0eb1246e3a5eabbbe566b44b10348939b7cc1b267567e8c3d07213853"}, + {file = "protobuf-5.29.1-cp38-cp38-win_amd64.whl", hash = "sha256:027fbcc48cea65a6b17028510fdd054147057fa78f4772eb547b9274e5219331"}, + {file = "protobuf-5.29.1-cp39-cp39-win32.whl", hash = "sha256:5a41deccfa5e745cef5c65a560c76ec0ed8e70908a67cc8f4da5fce588b50d57"}, + {file = "protobuf-5.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c"}, + {file = "protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0"}, + {file = "protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb"}, ] [[package]] @@ -2403,112 +2387,125 @@ test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "rpds-py" -version = "0.21.0" +version = "0.22.3" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" files = [ - {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, - {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, - {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, - {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, - {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, - {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, - {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, - {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, - {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, - {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, - {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, - {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, - {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, - {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, - {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, - {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, - {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, - {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, - {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, - {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, - {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, - {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, - {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, - {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, - {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, - {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, - {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, - {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, - {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, - {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, - {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, - {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, - {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, + {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, + {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, + {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, + {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, + {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, + {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, + {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, + {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, + {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, + {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, + {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, + {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, + {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, ] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -2584,7 +2581,7 @@ test = ["py-ecc (==6.0.0)", "pytest (>=6.2.5)", "pytest-benchmark (>=3.2.3)"] [[package]] name = "sw-utils" -version = "v0.6.25" +version = "v0.6.34" description = "StakeWise Python utils" optional = false python-versions = "^3.10" @@ -2603,8 +2600,8 @@ web3 = "==6.15.1" [package.source] type = "git" url = "https://github.com/stakewise/sw-utils.git" -reference = "v0.6.25" -resolved_reference = "202d0d3614f58be63a623733756b51d59765f251" +reference = "v0.6.34" +resolved_reference = "1ed42c002049d771f73f0975834cfe4e8c9adf50" [[package]] name = "tenacity" @@ -2622,13 +2619,43 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -2707,13 +2734,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.27.1" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, - {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies] @@ -2840,93 +2867,93 @@ files = [ [[package]] name = "yarl" -version = "1.17.2" +version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" files = [ - {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:93771146ef048b34201bfa382c2bf74c524980870bb278e6df515efaf93699ff"}, - {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8281db240a1616af2f9c5f71d355057e73a1409c4648c8949901396dc0a3c151"}, - {file = "yarl-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:170ed4971bf9058582b01a8338605f4d8c849bd88834061e60e83b52d0c76870"}, - {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc61b005f6521fcc00ca0d1243559a5850b9dd1e1fe07b891410ee8fe192d0c0"}, - {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871e1b47eec7b6df76b23c642a81db5dd6536cbef26b7e80e7c56c2fd371382e"}, - {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a58a2f2ca7aaf22b265388d40232f453f67a6def7355a840b98c2d547bd037f"}, - {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:736bb076f7299c5c55dfef3eb9e96071a795cb08052822c2bb349b06f4cb2e0a"}, - {file = "yarl-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fd51299e21da709eabcd5b2dd60e39090804431292daacbee8d3dabe39a6bc0"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:358dc7ddf25e79e1cc8ee16d970c23faee84d532b873519c5036dbb858965795"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:50d866f7b1a3f16f98603e095f24c0eeba25eb508c85a2c5939c8b3870ba2df8"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8b9c4643e7d843a0dca9cd9d610a0876e90a1b2cbc4c5ba7930a0d90baf6903f"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d63123bfd0dce5f91101e77c8a5427c3872501acece8c90df457b486bc1acd47"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4e76381be3d8ff96a4e6c77815653063e87555981329cf8f85e5be5abf449021"}, - {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:734144cd2bd633a1516948e477ff6c835041c0536cef1d5b9a823ae29899665b"}, - {file = "yarl-1.17.2-cp310-cp310-win32.whl", hash = "sha256:26bfb6226e0c157af5da16d2d62258f1ac578d2899130a50433ffee4a5dfa673"}, - {file = "yarl-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:76499469dcc24759399accd85ec27f237d52dec300daaca46a5352fcbebb1071"}, - {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:792155279dc093839e43f85ff7b9b6493a8eaa0af1f94f1f9c6e8f4de8c63500"}, - {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38bc4ed5cae853409cb193c87c86cd0bc8d3a70fd2268a9807217b9176093ac6"}, - {file = "yarl-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4a8c83f6fcdc327783bdc737e8e45b2e909b7bd108c4da1892d3bc59c04a6d84"}, - {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6d5fed96f0646bfdf698b0a1cebf32b8aae6892d1bec0c5d2d6e2df44e1e2d"}, - {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:782ca9c58f5c491c7afa55518542b2b005caedaf4685ec814fadfcee51f02493"}, - {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff6af03cac0d1a4c3c19e5dcc4c05252411bf44ccaa2485e20d0a7c77892ab6e"}, - {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a3f47930fbbed0f6377639503848134c4aa25426b08778d641491131351c2c8"}, - {file = "yarl-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1fa68a3c921365c5745b4bd3af6221ae1f0ea1bf04b69e94eda60e57958907f"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:187df91395c11e9f9dc69b38d12406df85aa5865f1766a47907b1cc9855b6303"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:93d1c8cc5bf5df401015c5e2a3ce75a5254a9839e5039c881365d2a9dcfc6dc2"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:11d86c6145ac5c706c53d484784cf504d7d10fa407cb73b9d20f09ff986059ef"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c42774d1d1508ec48c3ed29e7b110e33f5e74a20957ea16197dbcce8be6b52ba"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8e589379ef0407b10bed16cc26e7392ef8f86961a706ade0a22309a45414d7"}, - {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1056cadd5e850a1c026f28e0704ab0a94daaa8f887ece8dfed30f88befb87bb0"}, - {file = "yarl-1.17.2-cp311-cp311-win32.whl", hash = "sha256:be4c7b1c49d9917c6e95258d3d07f43cfba2c69a6929816e77daf322aaba6628"}, - {file = "yarl-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:ac8eda86cc75859093e9ce390d423aba968f50cf0e481e6c7d7d63f90bae5c9c"}, - {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd90238d3a77a0e07d4d6ffdebc0c21a9787c5953a508a2231b5f191455f31e9"}, - {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c74f0b0472ac40b04e6d28532f55cac8090e34c3e81f118d12843e6df14d0909"}, - {file = "yarl-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d486ddcaca8c68455aa01cf53d28d413fb41a35afc9f6594a730c9779545876"}, - {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25b7e93f5414b9a983e1a6c1820142c13e1782cc9ed354c25e933aebe97fcf2"}, - {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a0baff7827a632204060f48dca9e63fbd6a5a0b8790c1a2adfb25dc2c9c0d50"}, - {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:460024cacfc3246cc4d9f47a7fc860e4fcea7d1dc651e1256510d8c3c9c7cde0"}, - {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5870d620b23b956f72bafed6a0ba9a62edb5f2ef78a8849b7615bd9433384171"}, - {file = "yarl-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2941756754a10e799e5b87e2319bbec481ed0957421fba0e7b9fb1c11e40509f"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9611b83810a74a46be88847e0ea616794c406dbcb4e25405e52bff8f4bee2d0a"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cd7e35818d2328b679a13268d9ea505c85cd773572ebb7a0da7ccbca77b6a52e"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6b981316fcd940f085f646b822c2ff2b8b813cbd61281acad229ea3cbaabeb6b"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:688058e89f512fb7541cb85c2f149c292d3fa22f981d5a5453b40c5da49eb9e8"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56afb44a12b0864d17b597210d63a5b88915d680f6484d8d202ed68ade38673d"}, - {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:17931dfbb84ae18b287279c1f92b76a3abcd9a49cd69b92e946035cff06bcd20"}, - {file = "yarl-1.17.2-cp312-cp312-win32.whl", hash = "sha256:ff8d95e06546c3a8c188f68040e9d0360feb67ba8498baf018918f669f7bc39b"}, - {file = "yarl-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:4c840cc11163d3c01a9d8aad227683c48cd3e5be5a785921bcc2a8b4b758c4f3"}, - {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3294f787a437cb5d81846de3a6697f0c35ecff37a932d73b1fe62490bef69211"}, - {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1e7fedb09c059efee2533119666ca7e1a2610072076926fa028c2ba5dfeb78c"}, - {file = "yarl-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:da9d3061e61e5ae3f753654813bc1cd1c70e02fb72cf871bd6daf78443e9e2b1"}, - {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c012dceadc695ccf69301bfdccd1fc4472ad714fe2dd3c5ab4d2046afddf29"}, - {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f11fd61d72d93ac23718d393d2a64469af40be2116b24da0a4ca6922df26807e"}, - {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46c465ad06971abcf46dd532f77560181387b4eea59084434bdff97524444032"}, - {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef6eee1a61638d29cd7c85f7fd3ac7b22b4c0fabc8fd00a712b727a3e73b0685"}, - {file = "yarl-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4434b739a8a101a837caeaa0137e0e38cb4ea561f39cb8960f3b1e7f4967a3fc"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:752485cbbb50c1e20908450ff4f94217acba9358ebdce0d8106510859d6eb19a"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:17791acaa0c0f89323c57da7b9a79f2174e26d5debbc8c02d84ebd80c2b7bff8"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5c6ea72fe619fee5e6b5d4040a451d45d8175f560b11b3d3e044cd24b2720526"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db5ac3871ed76340210fe028f535392f097fb31b875354bcb69162bba2632ef4"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7a1606ba68e311576bcb1672b2a1543417e7e0aa4c85e9e718ba6466952476c0"}, - {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9bc27dd5cfdbe3dc7f381b05e6260ca6da41931a6e582267d5ca540270afeeb2"}, - {file = "yarl-1.17.2-cp313-cp313-win32.whl", hash = "sha256:52492b87d5877ec405542f43cd3da80bdcb2d0c2fbc73236526e5f2c28e6db28"}, - {file = "yarl-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:8e1bf59e035534ba4077f5361d8d5d9194149f9ed4f823d1ee29ef3e8964ace3"}, - {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c556fbc6820b6e2cda1ca675c5fa5589cf188f8da6b33e9fc05b002e603e44fa"}, - {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f44a4247461965fed18b2573f3a9eb5e2c3cad225201ee858726cde610daca"}, - {file = "yarl-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a3ede8c248f36b60227eb777eac1dbc2f1022dc4d741b177c4379ca8e75571a"}, - {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2654caaf5584449d49c94a6b382b3cb4a246c090e72453493ea168b931206a4d"}, - {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d41c684f286ce41fa05ab6af70f32d6da1b6f0457459a56cf9e393c1c0b2217"}, - {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2270d590997445a0dc29afa92e5534bfea76ba3aea026289e811bf9ed4b65a7f"}, - {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18662443c6c3707e2fc7fad184b4dc32dd428710bbe72e1bce7fe1988d4aa654"}, - {file = "yarl-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75ac158560dec3ed72f6d604c81090ec44529cfb8169b05ae6fcb3e986b325d9"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1fee66b32e79264f428dc8da18396ad59cc48eef3c9c13844adec890cd339db5"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:585ce7cd97be8f538345de47b279b879e091c8b86d9dbc6d98a96a7ad78876a3"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c019abc2eca67dfa4d8fb72ba924871d764ec3c92b86d5b53b405ad3d6aa56b0"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c6e659b9a24d145e271c2faf3fa6dd1fcb3e5d3f4e17273d9e0350b6ab0fe6e2"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d17832ba39374134c10e82d137e372b5f7478c4cceeb19d02ae3e3d1daed8721"}, - {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bc3003710e335e3f842ae3fd78efa55f11a863a89a72e9a07da214db3bf7e1f8"}, - {file = "yarl-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f5ffc6b7ace5b22d9e73b2a4c7305740a339fbd55301d52735f73e21d9eb3130"}, - {file = "yarl-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:48e424347a45568413deec6f6ee2d720de2cc0385019bedf44cd93e8638aa0ed"}, - {file = "yarl-1.17.2-py3-none-any.whl", hash = "sha256:dd7abf4f717e33b7487121faf23560b3a50924f80e4bef62b22dab441ded8f3b"}, - {file = "yarl-1.17.2.tar.gz", hash = "sha256:753eaaa0c7195244c84b5cc159dc8204b7fd99f716f11198f999f2332a86b178"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] @@ -2937,4 +2964,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "b4ca9945c8d9d6e522a61dff1f5d20752cdcb9d9f4322a6552e7e7eb911f8879" +content-hash = "e570721f2b150c4bd7116458fc4053f40cf958a949cdcea165f6e5edb5b3ce22" diff --git a/pyproject.toml b/pyproject.toml index 60f6b00..e11ea0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ package-mode = false [tool.poetry.dependencies] python = "^3.10" gql = {extras = ["requests"], version = "==3.5.0"} -sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.6.25"} +sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.6.34"} python-decouple = "==3.8" aiohttp = "==3.10.11" diff --git a/src/common/contracts.py b/src/common/contracts.py index 2596be2..166a6ba 100644 --- a/src/common/contracts.py +++ b/src/common/contracts.py @@ -1,9 +1,13 @@ import json import logging -from eth_typing import ChecksumAddress +from eth_typing import ChecksumAddress, HexStr +from hexbytes import HexBytes from web3 import Web3 from web3.contract.contract import ContractEvents, ContractFunctions +from web3.types import Wei + +from .typings import HarvestParams logger = logging.getLogger(__name__) @@ -24,3 +28,12 @@ def functions(self) -> ContractFunctions: @property def events(self) -> ContractEvents: return self.contract.events + + def encode_abi(self, fn_name: str, args: list | None = None) -> HexStr: + return self.contract.encodeABI(fn_name=fn_name, args=args) + + @staticmethod + def _get_zero_harvest_params() -> HarvestParams: + return HarvestParams( + rewards_root=HexBytes(b'\x00' * 32), reward=Wei(0), unlocked_mev_reward=Wei(0), proof=[] + ) diff --git a/src/common/networks.py b/src/common/networks.py index 65f0743..8347be6 100644 --- a/src/common/networks.py +++ b/src/common/networks.py @@ -1,19 +1,15 @@ -from dataclasses import dataclass -from enum import Enum +from dataclasses import asdict, dataclass from ens.constants import EMPTY_ADDR_HEX from eth_typing import ChecksumAddress +from sw_utils.networks import CHIADO, GNOSIS, HOLESKY, MAINNET +from sw_utils.networks import NETWORKS as BASE_NETWORKS +from sw_utils.networks import BaseNetworkConfig from web3 import Web3 ZERO_CHECKSUM_ADDRESS = Web3.to_checksum_address(EMPTY_ADDR_HEX) # noqa - -class Network(Enum): - MAINNET = 'mainnet' - HOLESKY = 'holesky' - GNOSIS = 'gnosis' - CHIADO = 'chiado' - SEPOLIA = 'sepolia' +SEPOLIA = 'sepolia' @dataclass @@ -29,92 +25,93 @@ class PriceNetworkConfig: PRICE_FEED_SENDER_CONTRACT_ADDRESS: ChecksumAddress +PRICE_NETWORKS: dict[str, PriceNetworkConfig] = { + MAINNET: PriceNetworkConfig( + # TARGET_CHAIN is not what eth_chainId returns. + # It is internal id used in PriceFeedSender contract. + TARGET_CHAIN=23, + # PriceFeedReceiver contract address on Arbitrum + TARGET_ADDRESS=Web3.to_checksum_address('0xbd335c16c94be8c4dd073ae376ddf78bec1858df'), + # PriceFeed contract address on Arbitrum + TARGET_PRICE_FEED_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0xba74737a078c05500dd98c970909e4a3b90c35c6' + ), + # PriceFeedSender contract address on Mainnet + PRICE_FEED_SENDER_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0xf7d4e7273e5015c96728a6b02f31c505ee184603' + ), + ), + SEPOLIA: PriceNetworkConfig( + # TARGET_CHAIN is not what eth_chainId returns. + # It is internal id used in PriceFeedSender contract. + TARGET_CHAIN=10003, + # PriceFeedReceiver contract address on Arbitrum Sepolia + TARGET_ADDRESS=Web3.to_checksum_address('0x744836a91f5151c6ef730eb7e07c232997debaaa'), + # PriceFeed contract address on Arbitrum Sepolia + TARGET_PRICE_FEED_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0x4026affabd9032bcc87fa05c02f088905f3dc09b' + ), + # PriceFeedSender contract address on Sepolia + PRICE_FEED_SENDER_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0xe572a8631a49ec4c334812bb692beecf934ac4e9' + ), + ), +} + + @dataclass -class NetworkConfig: +class NetworkConfig(BaseNetworkConfig): VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS: ChecksumAddress LEVERAGE_STRATEGY_CONTRACT_ADDRESS: ChecksumAddress + STRATEGY_REGISTRY_CONTRACT_ADDRESS: ChecksumAddress OSTOKEN_ESCROW_CONTRACT_ADDRESS: ChecksumAddress - PRICE_NETWORK_CONFIG: PriceNetworkConfig | None = None -NETWORKS: dict[Network, NetworkConfig] = { - Network.MAINNET: NetworkConfig( +NETWORKS: dict[str, NetworkConfig] = { + MAINNET: NetworkConfig( + **asdict(BASE_NETWORKS[MAINNET]), VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=Web3.to_checksum_address( '0x48cD14FDB8e72A03C8D952af081DBB127D6281fc' ), + STRATEGY_REGISTRY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, OSTOKEN_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( '0x09e84205DF7c68907e619D07aFD90143c5763605' ), - PRICE_NETWORK_CONFIG=( - PriceNetworkConfig( - # TARGET_CHAIN is not what eth_chainId returns. - # It is internal id used in PriceFeedSender contract. - TARGET_CHAIN=23, - # PriceFeedReceiver contract address on Arbitrum - TARGET_ADDRESS=Web3.to_checksum_address( - '0xbd335c16c94be8c4dd073ae376ddf78bec1858df' - ), - # PriceFeed contract address on Arbitrum - TARGET_PRICE_FEED_CONTRACT_ADDRESS=Web3.to_checksum_address( - '0xba74737a078c05500dd98c970909e4a3b90c35c6' - ), - # PriceFeedSender contract address on Mainnet - PRICE_FEED_SENDER_CONTRACT_ADDRESS=Web3.to_checksum_address( - '0xf7d4e7273e5015c96728a6b02f31c505ee184603' - ), - ) - ), ), - Network.HOLESKY: NetworkConfig( + HOLESKY: NetworkConfig( + **asdict(BASE_NETWORKS[HOLESKY]), VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0x8f48130b9b96B58035b4A9389eCDaBC00d59d0c8' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xdB38cfc6e98a34Cdc60c568f607417E646C75B34' ), + STRATEGY_REGISTRY_CONTRACT_ADDRESS=Web3.to_checksum_address( + '0xFc8E3E7c919b4392D9F5B27015688e49c80015f0' + ), OSTOKEN_ESCROW_CONTRACT_ADDRESS=Web3.to_checksum_address( '0x807305c086A99cbDBff07cB4256cE556d9d6F0af' ), ), - Network.GNOSIS: NetworkConfig( + GNOSIS: NetworkConfig( + **asdict(BASE_NETWORKS[GNOSIS]), VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xdEa72c54f63470349CE2dC12f8232FE00241abE6' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + STRATEGY_REGISTRY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), - Network.CHIADO: NetworkConfig( + CHIADO: NetworkConfig( + **asdict(BASE_NETWORKS[CHIADO]), VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=Web3.to_checksum_address( '0xe0Ae8B04922d6e3fA06c2496A94EF2875EFcC7BB' ), LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, + STRATEGY_REGISTRY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, ), - Network.SEPOLIA: NetworkConfig( - VAULT_USER_LTV_TRACKER_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - LEVERAGE_STRATEGY_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - OSTOKEN_ESCROW_CONTRACT_ADDRESS=ZERO_CHECKSUM_ADDRESS, - PRICE_NETWORK_CONFIG=( - PriceNetworkConfig( - # TARGET_CHAIN is not what eth_chainId returns. - # It is internal id used in PriceFeedSender contract. - TARGET_CHAIN=10003, - # PriceFeedReceiver contract address on Arbitrum Sepolia - TARGET_ADDRESS=Web3.to_checksum_address( - '0x744836a91f5151c6ef730eb7e07c232997debaaa' - ), - # PriceFeed contract address on Arbitrum Sepolia - TARGET_PRICE_FEED_CONTRACT_ADDRESS=Web3.to_checksum_address( - '0x4026affabd9032bcc87fa05c02f088905f3dc09b' - ), - # PriceFeedSender contract address on Sepolia - PRICE_FEED_SENDER_CONTRACT_ADDRESS=Web3.to_checksum_address( - '0xe572a8631a49ec4c334812bb692beecf934ac4e9' - ), - ) - ), - ), } diff --git a/src/common/settings.py b/src/common/settings.py index 1db6e18..f74fd86 100644 --- a/src/common/settings.py +++ b/src/common/settings.py @@ -1,6 +1,6 @@ from decouple import config -from src.common.networks import NETWORKS, Network +from src.common.networks import NETWORKS, PRICE_NETWORKS EXECUTION_ENDPOINT: str = config('EXECUTION_ENDPOINT', default='') HOT_WALLET_PRIVATE_KEY: str = config('HOT_WALLET_PRIVATE_KEY') @@ -9,5 +9,6 @@ EXECUTION_TRANSACTION_TIMEOUT: int = config('EXECUTION_TRANSACTION_TIMEOUT', default=300, cast=int) -NETWORK: Network = config('NETWORK', cast=Network) +NETWORK = config('NETWORK') network_config = NETWORKS[NETWORK] +price_network_config = PRICE_NETWORKS[NETWORK] diff --git a/src/ltv/typings.py b/src/common/typings.py similarity index 100% rename from src/ltv/typings.py rename to src/common/typings.py diff --git a/src/exit/abi/IEthVault.json b/src/exit/abi/IEthVault.json new file mode 100644 index 0000000..735e479 --- /dev/null +++ b/src/exit/abi/IEthVault.json @@ -0,0 +1,1288 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "CheckpointCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "ExitQueueEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "prevPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPositionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawnAssets", + "type": "uint256" + } + ], + "name": "ExitedAssetsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "penalty", + "type": "uint256" + } + ], + "name": "ExitingAssetsPenalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "FeeRecipientUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "FeeSharesMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "keysManager", + "type": "address" + } + ], + "name": "KeysManagerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "metadataIpfsHash", + "type": "string" + } + ], + "name": "MetadataUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "OsTokenBurned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "receivedAssets", + "type": "uint256" + } + ], + "name": "OsTokenLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "OsTokenMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "OsTokenRedeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Redeemed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "V2ExitQueueEntered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + } + ], + "name": "ValidatorRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorsManager", + "type": "address" + } + ], + "name": "ValidatorsManagerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "validatorsRoot", + "type": "bytes32" + } + ], + "name": "ValidatorsRootUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "osTokenShares", + "type": "uint128" + } + ], + "name": "burnOsToken", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitQueueIndex", + "type": "uint256" + } + ], + "name": "calculateExitedAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "leftTickets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitedTickets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitedAssets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "capacity", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exitQueueIndex", + "type": "uint256" + } + ], + "name": "claimExitedAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "depositAndMintOsToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "enterExitQueue", + "outputs": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feePercent", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "positionTicket", + "type": "uint256" + } + ], + "name": "getExitQueueIndex", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "params", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "isStateUpdateRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "liquidateOsToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mevEscrow", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + } + ], + "name": "mintOsToken", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "osTokenPositions", + "outputs": [ + { + "internalType": "uint128", + "name": "shares", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "queuedShares", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "receiveFromMevEscrow", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "redeemOsToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "validatorsRegistryRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "validators", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "internalType": "struct IKeeperValidators.ApprovalParams", + "name": "keeperParams", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "validatorsManagerSignature", + "type": "bytes" + } + ], + "name": "registerValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeRecipient", + "type": "address" + } + ], + "name": "setFeeRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "metadataIpfsHash", + "type": "string" + } + ], + "name": "setMetadata", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validatorsManager", + "type": "address" + } + ], + "name": "setValidatorsManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalExitingAssets", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "int160", + "name": "reward", + "type": "int160" + }, + { + "internalType": "uint160", + "name": "unlockedMevReward", + "type": "uint160" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "internalType": "struct IKeeperRewards.HarvestParams", + "name": "harvestParams", + "type": "tuple" + } + ], + "name": "updateState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "int160", + "name": "reward", + "type": "int160" + }, + { + "internalType": "uint160", + "name": "unlockedMevReward", + "type": "uint160" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "internalType": "struct IKeeperRewards.HarvestParams", + "name": "harvestParams", + "type": "tuple" + } + ], + "name": "updateStateAndDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "osTokenShares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "referrer", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "int160", + "name": "reward", + "type": "int160" + }, + { + "internalType": "uint160", + "name": "unlockedMevReward", + "type": "uint160" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "internalType": "struct IKeeperRewards.HarvestParams", + "name": "harvestParams", + "type": "tuple" + } + ], + "name": "updateStateAndDepositAndMintOsToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawableAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/exit/abi/IKeeper.json b/src/exit/abi/IKeeper.json new file mode 100644 index 0000000..3f89a72 --- /dev/null +++ b/src/exit/abi/IKeeper.json @@ -0,0 +1,737 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "configIpfsHash", + "type": "string" + } + ], + "name": "ConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "name": "ExitSignaturesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "int256", + "name": "totalAssetsDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "unlockedMevDelta", + "type": "uint256" + } + ], + "name": "Harvested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oracles", + "type": "uint256" + } + ], + "name": "RewardsMinOraclesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "avgRewardPerSecond", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "updateTimestamp", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "string", + "name": "rewardsIpfsHash", + "type": "string" + } + ], + "name": "RewardsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "name": "ValidatorsApproval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oracles", + "type": "uint256" + } + ], + "name": "ValidatorsMinOraclesUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "addOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "validatorsRegistryRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "validators", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + } + ], + "internalType": "struct IKeeperValidators.ApprovalParams", + "name": "params", + "type": "tuple" + } + ], + "name": "approveValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "canHarvest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "canUpdateRewards", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "exitSignaturesNonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "int160", + "name": "reward", + "type": "int160" + }, + { + "internalType": "uint160", + "name": "unlockedMevReward", + "type": "uint160" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + } + ], + "internalType": "struct IKeeperRewards.HarvestParams", + "name": "params", + "type": "tuple" + } + ], + "name": "harvest", + "outputs": [ + { + "internalType": "int256", + "name": "totalAssetsDelta", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "unlockedMevDelta", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "harvested", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "isCollateralized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "isHarvestRequired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "isOracle", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastRewardsTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "prevRewardsRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "removeOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "rewards", + "outputs": [ + { + "internalType": "int192", + "name": "assets", + "type": "int192" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsMinOracles", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsNonce", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_rewardsMinOracles", + "type": "uint256" + } + ], + "name": "setRewardsMinOracles", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_validatorsMinOracles", + "type": "uint256" + } + ], + "name": "setValidatorsMinOracles", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalOracles", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "unlockedMevRewards", + "outputs": [ + { + "internalType": "uint192", + "name": "assets", + "type": "uint192" + }, + { + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "configIpfsHash", + "type": "string" + } + ], + "name": "updateConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "string", + "name": "exitSignaturesIpfsHash", + "type": "string" + }, + { + "internalType": "bytes", + "name": "oraclesSignatures", + "type": "bytes" + } + ], + "name": "updateExitSignatures", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "rewardsRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "avgRewardPerSecond", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "updateTimestamp", + "type": "uint64" + }, + { + "internalType": "string", + "name": "rewardsIpfsHash", + "type": "string" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "internalType": "struct IKeeperRewards.RewardsUpdateParams", + "name": "params", + "type": "tuple" + } + ], + "name": "updateRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsMinOracles", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/exit/abi/IStrategyRegistry.json b/src/exit/abi/IStrategyRegistry.json new file mode 100644 index 0000000..f4429f8 --- /dev/null +++ b/src/exit/abi/IStrategyRegistry.json @@ -0,0 +1,378 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessDenied", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStrategyId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStrategyProxyId", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ValueNotChanged", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "configName", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "name": "StrategyConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "StrategyProxyAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "StrategyUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "addStrategyProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "configName", + "type": "string" + } + ], + "name": "getStrategyConfig", + "outputs": [ + { + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "configName", + "type": "string" + }, + { + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "name": "setStrategyConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategy", + "type": "address" + } + ], + "name": "strategies", + "outputs": [ + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "strategyProxies", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + } + ], + "name": "strategyProxyIdToProxy", + "outputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/exit/abi/Multicall.json b/src/exit/abi/Multicall.json new file mode 100644 index 0000000..bc6b330 --- /dev/null +++ b/src/exit/abi/Multicall.json @@ -0,0 +1,440 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "returnData", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { + "internalType": "uint256", + "name": "basefee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "chainid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { + "internalType": "address", + "name": "coinbase", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { + "internalType": "uint256", + "name": "difficulty", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "gaslimit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/src/exit/contracts.py b/src/exit/contracts.py index 8408705..6e3bb4d 100644 --- a/src/exit/contracts.py +++ b/src/exit/contracts.py @@ -2,7 +2,7 @@ from eth_typing import ChecksumAddress from hexbytes import HexBytes -from web3.types import Wei +from web3.types import BlockNumber, HexStr, Wei from src.common.contracts import ContractWrapper from src.common.settings import network_config @@ -15,8 +15,8 @@ class LeverageStrategyContract(ContractWrapper): - def can_force_enter_exit_queue(self, vault: ChecksumAddress, user: ChecksumAddress) -> bool: - return self.contract.functions.canForceEnterExitQueue(vault, user).call() + def strategy_id(self) -> str: + return self.contract.functions.strategyId().call() def force_enter_exit_queue( self, @@ -46,14 +46,74 @@ def claim_exited_assets( ).transact() +class StrategiesRegistryContract(ContractWrapper): + def get_vault_ltv_percent(self, strategy_id: str) -> int: + return self.contract.functions.getStrategyConfig( + strategy_id, 'vaultForceExitLtvPercent' + ).call() + + def get_borrow_ltv_percent(self, strategy_id: str) -> int: + return self.contract.functions.getStrategyConfig( + strategy_id, 'borrowForceExitLtvPercent' + ).call() + + +class VaultContract(ContractWrapper): + ... + + +class KeeperContract(ContractWrapper): + abi_path = 'abi/IKeeper.json' + + async def can_harvest( + self, vault: ChecksumAddress, block_number: BlockNumber | None = None + ) -> bool: + return await self.contract.functions.canHarvest(vault).call(block_identifier=block_number) + + +class MulticallContract(ContractWrapper): + def aggregate( + self, + data: list[tuple[ChecksumAddress, HexStr]], + block_number: BlockNumber | None = None, + ) -> tuple[BlockNumber, list]: + return self.contract.functions.aggregate(data).call(block_identifier=block_number) + + leverage_strategy_contract = LeverageStrategyContract( abi_path=f'{ABI_DIR}/ILeverageStrategy.json', address=network_config.LEVERAGE_STRATEGY_CONTRACT_ADDRESS, client=execution_client, ) +strategy_registry_contract = StrategiesRegistryContract( + abi_path=f'{ABI_DIR}/IStrategyRegistry.json', + address=network_config.STRATEGY_REGISTRY_CONTRACT_ADDRESS, + client=execution_client, +) + ostoken_vault_escrow_contract = OsTokenVaultEscrowContract( abi_path=f'{ABI_DIR}/IOsTokenVaultEscrow.json', address=network_config.OSTOKEN_ESCROW_CONTRACT_ADDRESS, client=execution_client, ) + +keeper_contract = KeeperContract( + abi_path=f'{ABI_DIR}/IKeeper.json', + address=network_config.KEEPER_CONTRACT_ADDRESS, + client=execution_client, +) + +multicall_contract = MulticallContract( + abi_path=f'{ABI_DIR}/Multicall.json', + address=network_config.MULTICALL_CONTRACT_ADDRESS, + client=execution_client, +) + + +def get_vault_contract(address: ChecksumAddress) -> VaultContract: + return VaultContract( + abi_path=f'{ABI_DIR}/IEthVault.json', + address=address, + client=execution_client, + ) diff --git a/src/exit/graph.py b/src/exit/graph.py index 6290734..5cb0523 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -6,12 +6,15 @@ from .typings import LeveragePosition, OsTokenExitRequest -def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosition]: +def graph_get_leverage_positions( + borrow_ltv: int, block_number: BlockNumber +) -> list[LeveragePosition]: query = gql( """ - query PositionsQuery($block: BigInt) { + query PositionsQuery($borrowLTV: String, $block: BigInt) { leverageStrategyPositions( block: { number: $block }, + where: { borrowLtv_gt: $borrowLTV }, orderBy: borrowLtv, orderDirection: desc ) { @@ -24,7 +27,7 @@ def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosi } """ ) - params = {'block': block_number} + params = {'block': block_number, 'borrowLTV': str(borrow_ltv)} response = graph_client.execute(query, params) result = [] for data in response['leverageStrategyPositions']: # pylint: disable=unsubscriptable-object @@ -39,14 +42,14 @@ def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosi def graph_get_allocators( - addresses: list[ChecksumAddress], block_number: BlockNumber + ltv: int, addresses: list[ChecksumAddress], block_number: BlockNumber ) -> list[ChecksumAddress]: query = gql( """ - query AllocatorsQuery($addresses: [String], $block: BigInt) { + query AllocatorsQuery($ltv: String, $addresses: [String], $block: BigInt) { allocators( block: { number: $block }, - where: {address_in: $addresses}, + where: { ltv_gt: $ltv, address_in: $addresses }, orderBy: ltv, orderDirection: desc ) { @@ -55,7 +58,11 @@ def graph_get_allocators( } """ ) - params = {'addresses': [address.lower() for address in addresses], 'block': block_number} + params = { + 'ltv': ltv, + 'addresses': [address.lower() for address in addresses], + 'block': block_number, + } response = graph_client.execute(query, params) result = [] for data in response['allocators']: # pylint: disable=unsubscriptable-object diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 39ec26e..c5be9d7 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -2,11 +2,22 @@ from eth_typing import ChecksumAddress, HexStr from web3 import Web3 +from web3.types import BlockNumber from src.common.settings import EXECUTION_TRANSACTION_TIMEOUT +from src.common.typings import HarvestParams +from src.ltv.graph import graph_get_harvest_params from .clients import execution_client -from .contracts import leverage_strategy_contract, ostoken_vault_escrow_contract +from .contracts import ( + VaultContract, + get_vault_contract, + keeper_contract, + leverage_strategy_contract, + multicall_contract, + ostoken_vault_escrow_contract, + strategy_registry_contract, +) from .graph import ( graph_get_allocators, graph_get_leverage_positions, @@ -26,14 +37,29 @@ def force_exits() -> None: logger.debug('Current block: %d', block['number']) block_number = block['number'] # force exit leverage positions - leverage_positions = graph_get_leverage_positions(block_number=block_number) + + strategy_id = leverage_strategy_contract.strategy_id() + borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) + vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) + + leverage_positions = graph_get_leverage_positions( + borrow_ltv=borrow_ltv, block_number=block_number + ) logger.info('Checking %d leverage positions...', len(leverage_positions)) + vault_to_harvest_params: dict[ChecksumAddress, HarvestParams | None] = {} # check by position borrow ltv for position in leverage_positions: - if leverage_strategy_contract.can_force_enter_exit_queue( + harvest_params = vault_to_harvest_params.get(position.vault) + if not harvest_params: + harvest_params = graph_get_harvest_params(position.vault) + vault_to_harvest_params[position.vault] = harvest_params + + if can_force_enter_exit_queue( vault=position.vault, user=position.user, + harvest_params=harvest_params, + block_number=block_number, ): logger.info( 'Force exiting leverage positions: vault=%s, user=%s...', @@ -50,21 +76,27 @@ def force_exits() -> None: position.vault, position.user, ) - else: - # position sorted by ltv and next will have lower ltv - break # check by position proxy ltv proxy_to_position = {position.proxy: position for position in leverage_positions} - allocators = graph_get_allocators(list(proxy_to_position.keys()), block_number=block_number) + allocators = graph_get_allocators( + ltv=vault_ltv, addresses=list(proxy_to_position.keys()), block_number=block_number + ) leverage_positions_by_ltv = [] for allocator in allocators: leverage_positions_by_ltv.append(proxy_to_position[allocator]) for position in leverage_positions_by_ltv: - if leverage_strategy_contract.can_force_enter_exit_queue( + harvest_params = vault_to_harvest_params.get(position.vault) + if not harvest_params: + harvest_params = graph_get_harvest_params(position.vault) + vault_to_harvest_params[position.vault] = harvest_params + + if can_force_enter_exit_queue( vault=position.vault, user=position.user, + harvest_params=harvest_params, + block_number=block_number, ): logger.info( 'Force exiting leverage positions: vault=%s, user=%s...', @@ -80,9 +112,6 @@ def force_exits() -> None: position.vault, position.user, ) - else: - # position sorted by ltv and next will have lower ltv - break # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() @@ -108,6 +137,49 @@ def force_exits() -> None: ) +def _encode_update_state_call( + vault_contract: VaultContract, harvest_params: HarvestParams +) -> HexStr: + return vault_contract.encode_abi( + fn_name='updateState', + args=[ + ( + harvest_params.rewards_root, + harvest_params.reward, + harvest_params.unlocked_mev_reward, + harvest_params.proof, + ) + ], + ) + + +def can_force_enter_exit_queue( + vault: ChecksumAddress, + user: ChecksumAddress, + harvest_params: HarvestParams | None, + block_number: BlockNumber, +) -> bool: + vault_contract = get_vault_contract(vault) + calls = [] + update_state_call = None + if harvest_params and keeper_contract.can_harvest(vault, block_number): + update_state_call = ( + vault, + _encode_update_state_call(vault_contract, harvest_params), + ) + calls.append(update_state_call) + + can_force_enter_exit_queue_call = leverage_strategy_contract.encode_abi( + fn_name='canForceEnterExitQueue', args=[vault, user] + ) + calls.append((leverage_strategy_contract.address, can_force_enter_exit_queue_call)) + # fetch data + _, response = multicall_contract.aggregate(calls, block_number) + if update_state_call: + response.pop(0) + return bool(Web3.to_int(response.pop(0))) + + def _force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: try: tx = leverage_strategy_contract.force_enter_exit_queue( diff --git a/src/ltv/contracts.py b/src/ltv/contracts.py index 30076ea..b7430c0 100644 --- a/src/ltv/contracts.py +++ b/src/ltv/contracts.py @@ -3,13 +3,12 @@ from eth_typing import ChecksumAddress from hexbytes import HexBytes from web3 import Web3 -from web3.types import Wei from src.common.contracts import ContractWrapper from src.common.settings import network_config +from src.common.typings import HarvestParams from .clients import execution_client -from .typings import HarvestParams logger = logging.getLogger(__name__) @@ -56,12 +55,6 @@ def update_vault_max_ltv_user( ), ).transact() - @staticmethod - def _get_zero_harvest_params() -> HarvestParams: - return HarvestParams( - rewards_root=HexBytes(b'\x00' * 32), reward=Wei(0), unlocked_mev_reward=Wei(0), proof=[] - ) - vault_user_ltv_tracker_contract = VaultUserLTVTrackerContract( abi_path=f'{ABI_DIR}/IVaultUserLtvTracker.json', diff --git a/src/ltv/graph.py b/src/ltv/graph.py index fbdcef9..a9a2265 100644 --- a/src/ltv/graph.py +++ b/src/ltv/graph.py @@ -6,8 +6,9 @@ from web3 import Web3 from web3.types import Wei +from src.common.typings import HarvestParams + from .clients import graph_client -from .typings import HarvestParams logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/price/contracts.py b/src/price/contracts.py index 4de1dea..e7132a1 100644 --- a/src/price/contracts.py +++ b/src/price/contracts.py @@ -1,6 +1,6 @@ from src.common.contracts import ContractWrapper +from src.common.settings import price_network_config from src.price.clients import sender_execution_client, target_execution_client -from src.price.settings import price_network_config ABI_DIR = 'src/price/abi' diff --git a/src/price/settings.py b/src/price/settings.py index 2bd49f7..5d55f44 100644 --- a/src/price/settings.py +++ b/src/price/settings.py @@ -1,11 +1,4 @@ -from typing import cast - from decouple import config -from src.common.networks import PriceNetworkConfig -from src.common.settings import network_config - sender_execution_endpoint: str = config('SENDER_EXECUTION_ENDPOINT') target_execution_endpoint: str = config('TARGET_EXECUTION_ENDPOINT') - -price_network_config = cast(PriceNetworkConfig, network_config.PRICE_NETWORK_CONFIG) diff --git a/src/price/tasks.py b/src/price/tasks.py index 893f143..1540e68 100644 --- a/src/price/tasks.py +++ b/src/price/tasks.py @@ -2,9 +2,9 @@ import time from datetime import timedelta +from src.common.settings import price_network_config from src.price.clients import hot_wallet_account, sender_execution_client from src.price.contracts import price_feed_sender_contract, target_price_feed_contract -from src.price.settings import price_network_config logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) From 3d642571711d01f7b780afb4568a3f6fc2089043 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 5 Dec 2024 22:39:56 +0300 Subject: [PATCH 07/19] Small bunch of fixes --- src/common/networks.py | 5 ++++- src/common/settings.py | 3 +-- src/exit/contracts.py | 13 +++++++------ src/exit/graph.py | 12 ++++++------ src/exit/tasks.py | 27 ++++++++++++++++++--------- src/price/contracts.py | 2 +- src/price/settings.py | 7 +++++++ src/price/tasks.py | 2 +- 8 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/common/networks.py b/src/common/networks.py index 8347be6..1b4b8a0 100644 --- a/src/common/networks.py +++ b/src/common/networks.py @@ -25,7 +25,7 @@ class PriceNetworkConfig: PRICE_FEED_SENDER_CONTRACT_ADDRESS: ChecksumAddress -PRICE_NETWORKS: dict[str, PriceNetworkConfig] = { +PRICE_NETWORKS: dict[str, PriceNetworkConfig | None] = { MAINNET: PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. # It is internal id used in PriceFeedSender contract. @@ -41,6 +41,9 @@ class PriceNetworkConfig: '0xf7d4e7273e5015c96728a6b02f31c505ee184603' ), ), + HOLESKY: None, + GNOSIS: None, + CHIADO: None, SEPOLIA: PriceNetworkConfig( # TARGET_CHAIN is not what eth_chainId returns. # It is internal id used in PriceFeedSender contract. diff --git a/src/common/settings.py b/src/common/settings.py index f74fd86..053588e 100644 --- a/src/common/settings.py +++ b/src/common/settings.py @@ -1,6 +1,6 @@ from decouple import config -from src.common.networks import NETWORKS, PRICE_NETWORKS +from src.common.networks import NETWORKS EXECUTION_ENDPOINT: str = config('EXECUTION_ENDPOINT', default='') HOT_WALLET_PRIVATE_KEY: str = config('HOT_WALLET_PRIVATE_KEY') @@ -11,4 +11,3 @@ NETWORK = config('NETWORK') network_config = NETWORKS[NETWORK] -price_network_config = PRICE_NETWORKS[NETWORK] diff --git a/src/exit/contracts.py b/src/exit/contracts.py index 6e3bb4d..bba281a 100644 --- a/src/exit/contracts.py +++ b/src/exit/contracts.py @@ -2,6 +2,7 @@ from eth_typing import ChecksumAddress from hexbytes import HexBytes +from web3 import Web3 from web3.types import BlockNumber, HexStr, Wei from src.common.contracts import ContractWrapper @@ -48,14 +49,16 @@ def claim_exited_assets( class StrategiesRegistryContract(ContractWrapper): def get_vault_ltv_percent(self, strategy_id: str) -> int: - return self.contract.functions.getStrategyConfig( + value = self.contract.functions.getStrategyConfig( strategy_id, 'vaultForceExitLtvPercent' ).call() + return Web3.to_int(value) def get_borrow_ltv_percent(self, strategy_id: str) -> int: - return self.contract.functions.getStrategyConfig( + value = self.contract.functions.getStrategyConfig( strategy_id, 'borrowForceExitLtvPercent' ).call() + return Web3.to_int(value) class VaultContract(ContractWrapper): @@ -65,10 +68,8 @@ class VaultContract(ContractWrapper): class KeeperContract(ContractWrapper): abi_path = 'abi/IKeeper.json' - async def can_harvest( - self, vault: ChecksumAddress, block_number: BlockNumber | None = None - ) -> bool: - return await self.contract.functions.canHarvest(vault).call(block_identifier=block_number) + def can_harvest(self, vault: ChecksumAddress, block_number: BlockNumber | None = None) -> bool: + return self.contract.functions.canHarvest(vault).call(block_identifier=block_number) class MulticallContract(ContractWrapper): diff --git a/src/exit/graph.py b/src/exit/graph.py index 5cb0523..280cbcf 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -7,11 +7,11 @@ def graph_get_leverage_positions( - borrow_ltv: int, block_number: BlockNumber + borrow_ltv: float, block_number: BlockNumber ) -> list[LeveragePosition]: query = gql( """ - query PositionsQuery($borrowLTV: String, $block: BigInt) { + query PositionsQuery($block: Int, $borrowLTV: String) { leverageStrategyPositions( block: { number: $block }, where: { borrowLtv_gt: $borrowLTV }, @@ -42,11 +42,11 @@ def graph_get_leverage_positions( def graph_get_allocators( - ltv: int, addresses: list[ChecksumAddress], block_number: BlockNumber + ltv: float, addresses: list[ChecksumAddress], block_number: BlockNumber ) -> list[ChecksumAddress]: query = gql( """ - query AllocatorsQuery($ltv: String, $addresses: [String], $block: BigInt) { + query AllocatorsQuery($ltv: String, $addresses: [String], $block: Int) { allocators( block: { number: $block }, where: { ltv_gt: $ltv, address_in: $addresses }, @@ -59,7 +59,7 @@ def graph_get_allocators( """ ) params = { - 'ltv': ltv, + 'ltv': str(ltv), 'addresses': [address.lower() for address in addresses], 'block': block_number, } @@ -75,7 +75,7 @@ def graph_get_allocators( def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsTokenExitRequest]: query = gql( """ - query ExitRequestsQuery($ltv: String, $block: BigInt) { + query ExitRequestsQuery($ltv: String, $block: Int) { osTokenExitRequests( block: { number: $block }, where: {ltv_gt: $ltv} diff --git a/src/exit/tasks.py b/src/exit/tasks.py index c5be9d7..0bb9ea9 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -27,6 +27,8 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +WAD = 10**18 + def force_exits() -> None: """ @@ -36,11 +38,15 @@ def force_exits() -> None: block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) block_number = block['number'] - # force exit leverage positions + handle_leverage_postions(block_number) + handel_exit_position(block_number) + +def handle_leverage_postions(block_number: BlockNumber) -> None: + # force exit leverage positions strategy_id = leverage_strategy_contract.strategy_id() - borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) - vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) + borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD + vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD leverage_positions = graph_get_leverage_positions( borrow_ltv=borrow_ltv, block_number=block_number @@ -103,16 +109,19 @@ def force_exits() -> None: position.vault, position.user, ) - leverage_strategy_contract.force_enter_exit_queue( + tx_hash = _force_enter_exit_queue( vault=position.vault, user=position.user, ) - logger.info( - 'Successfully exited leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) + if tx_hash: + logger.info( + 'Successfully exited leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + +def handel_exit_position(block_number: BlockNumber) -> None: # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() max_ltv_percent = max_ltv_percent // 10**18 * 100 diff --git a/src/price/contracts.py b/src/price/contracts.py index e7132a1..4de1dea 100644 --- a/src/price/contracts.py +++ b/src/price/contracts.py @@ -1,6 +1,6 @@ from src.common.contracts import ContractWrapper -from src.common.settings import price_network_config from src.price.clients import sender_execution_client, target_execution_client +from src.price.settings import price_network_config ABI_DIR = 'src/price/abi' diff --git a/src/price/settings.py b/src/price/settings.py index 5d55f44..ecbd67a 100644 --- a/src/price/settings.py +++ b/src/price/settings.py @@ -1,4 +1,11 @@ +from typing import cast + from decouple import config +from src.common.networks import PRICE_NETWORKS, PriceNetworkConfig +from src.common.settings import NETWORK + sender_execution_endpoint: str = config('SENDER_EXECUTION_ENDPOINT') target_execution_endpoint: str = config('TARGET_EXECUTION_ENDPOINT') + +price_network_config = cast(PriceNetworkConfig, PRICE_NETWORKS[NETWORK]) diff --git a/src/price/tasks.py b/src/price/tasks.py index 1540e68..893f143 100644 --- a/src/price/tasks.py +++ b/src/price/tasks.py @@ -2,9 +2,9 @@ import time from datetime import timedelta -from src.common.settings import price_network_config from src.price.clients import hot_wallet_account, sender_execution_client from src.price.contracts import price_feed_sender_contract, target_price_feed_contract +from src.price.settings import price_network_config logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) From dd4353511523558b86af24b3a2112965518e3079 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Fri, 6 Dec 2024 00:22:49 +0300 Subject: [PATCH 08/19] Claim exited assets --- src/exit/graph.py | 33 ++++++++++++++---- src/exit/tasks.py | 85 +++++++++++++++++++++++++++++++++++++++++++++ src/exit/typings.py | 15 ++++++++ 3 files changed, 126 insertions(+), 7 deletions(-) diff --git a/src/exit/graph.py b/src/exit/graph.py index 280cbcf..310154c 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -3,7 +3,7 @@ from web3.types import BlockNumber, ChecksumAddress, Wei from .clients import graph_client -from .typings import LeveragePosition, OsTokenExitRequest +from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest def graph_get_leverage_positions( @@ -23,6 +23,14 @@ def graph_get_leverage_positions( vault { id } + exitRequest { + positionTicket + timestamp + exitQueueIndex + isClaimable + exitedAssets + totalAssets + } } } """ @@ -31,13 +39,24 @@ def graph_get_leverage_positions( response = graph_client.execute(query, params) result = [] for data in response['leverageStrategyPositions']: # pylint: disable=unsubscriptable-object - result.append( - LeveragePosition( - vault=Web3.to_checksum_address(data['vault']['id']), - user=Web3.to_checksum_address(data['user']), - proxy=Web3.to_checksum_address(data['proxy']), - ) + position = LeveragePosition( + vault=Web3.to_checksum_address(data['vault']['id']), + user=Web3.to_checksum_address(data['user']), + proxy=Web3.to_checksum_address(data['proxy']), ) + if data['exitRequest']: + request_data = data['exitRequest'] + exit_request = ExitRequest( + position_ticket=request_data['positionTicket'], + timestamp=request_data['timestamp'], + exit_queue_index=request_data['exitQueueIndex'], + is_claimable=request_data['isClaimable'], + exited_assets=request_data['exitedAssets'], + total_assets=request_data['totalAssets'], + ) + position.exit_request = exit_request + + result.append(position) return result diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 0bb9ea9..4f31f8a 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -1,4 +1,5 @@ import logging +from typing import cast from eth_typing import ChecksumAddress, HexStr from web3 import Web3 @@ -23,6 +24,7 @@ graph_get_leverage_positions, graph_ostoken_exit_requests, ) +from .typings import ExitRequest, LeveragePosition logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -67,6 +69,26 @@ def handle_leverage_postions(block_number: BlockNumber) -> None: harvest_params=harvest_params, block_number=block_number, ): + # claim active exit request + if position.exit_request and position.exit_request.can_be_claimed: + logger.info( + 'Claiming exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + tx_hash = claim_exited_assets( + position=position, + harvest_params=harvest_params, + block_number=block_number, + ) + if tx_hash: + logger.info( + 'Successfully claimed exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + else: + continue logger.info( 'Force exiting leverage positions: vault=%s, user=%s...', position.vault, @@ -104,6 +126,26 @@ def handle_leverage_postions(block_number: BlockNumber) -> None: harvest_params=harvest_params, block_number=block_number, ): + # claim active exit request + if position.exit_request and position.exit_request.can_be_claimed: + logger.info( + 'Claiming exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + tx_hash = claim_exited_assets( + position=position, + harvest_params=harvest_params, + block_number=block_number, + ) + if tx_hash: + logger.info( + 'Successfully claimed exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + else: + continue logger.info( 'Force exiting leverage positions: vault=%s, user=%s...', position.vault, @@ -189,6 +231,49 @@ def can_force_enter_exit_queue( return bool(Web3.to_int(response.pop(0))) +def claim_exited_assets( + position: LeveragePosition, + harvest_params: HarvestParams | None, + block_number: BlockNumber, +) -> HexStr | None: + vault = position.vault + vault_contract = get_vault_contract(vault) + calls = [] + if harvest_params and keeper_contract.can_harvest(vault, block_number): + update_state_call = ( + vault, + _encode_update_state_call(vault_contract, harvest_params), + ) + calls.append(update_state_call) + + exit_request = position.exit_request + exit_request = cast(ExitRequest, exit_request) + claim_call = vault_contract.encode_abi( + fn_name='claimExitedAssets', + args=[exit_request.position_ticket, exit_request.timestamp, exit_request.exit_queue_index], + ) + calls.append((vault_contract.address, claim_call)) + try: + tx = multicall_contract.functions.aggregate(calls).transact() + except Exception as e: + logger.error( + 'Failed to claim exited assets; vault=%s, user=%s %s: ', vault, position.user, e + ) + logger.exception(e) + return None + + tx_hash = Web3.to_hex(tx) + logger.info('Waiting for transaction %s confirmation', tx_hash) + tx_receipt = execution_client.eth.wait_for_transaction_receipt( + tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + ) + if not tx_receipt['status']: + logger.error('Exited assets claim transaction failed') + return None + + return tx_hash + + def _force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: try: tx = leverage_strategy_contract.force_enter_exit_queue( diff --git a/src/exit/typings.py b/src/exit/typings.py index 44c69c4..4862c6b 100644 --- a/src/exit/typings.py +++ b/src/exit/typings.py @@ -3,11 +3,26 @@ from web3.types import ChecksumAddress, Wei +@dataclass +class ExitRequest: + position_ticket: int + timestamp: int + exit_queue_index: int + is_claimable: bool + exited_assets: Wei + total_assets: Wei + + @property + def can_be_claimed(self) -> bool: + return self.is_claimable and self.exited_assets == self.total_assets + + @dataclass class LeveragePosition: user: ChecksumAddress vault: ChecksumAddress proxy: ChecksumAddress + exit_request: ExitRequest | None = None @dataclass From dfa1562bbc9ef7ec5a9795bff4469e30027ef810 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Fri, 6 Dec 2024 16:16:02 +0300 Subject: [PATCH 09/19] Add graph pagination --- src/common/settings.py | 2 ++ src/exit/graph.py | 57 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/common/settings.py b/src/common/settings.py index 053588e..04ab711 100644 --- a/src/common/settings.py +++ b/src/common/settings.py @@ -5,6 +5,8 @@ EXECUTION_ENDPOINT: str = config('EXECUTION_ENDPOINT', default='') HOT_WALLET_PRIVATE_KEY: str = config('HOT_WALLET_PRIVATE_KEY') +GRAPH_PAGE_SIZE: int = config('GRAPH_PAGE_SIZE', default=100, cast=int) + EXECUTION_TRANSACTION_TIMEOUT: int = config('EXECUTION_TRANSACTION_TIMEOUT', default=300, cast=int) diff --git a/src/exit/graph.py b/src/exit/graph.py index 310154c..b416050 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -1,22 +1,47 @@ +from typing import Any + from gql import gql +from graphql import DocumentNode from web3 import Web3 from web3.types import BlockNumber, ChecksumAddress, Wei +from src.common.settings import GRAPH_PAGE_SIZE + from .clients import graph_client from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest +def paginate_query(query: DocumentNode, params: dict) -> list: + res: list[Any] = [] + page_res = None + first = GRAPH_PAGE_SIZE + skip = 0 + + while page_res is None or len(page_res): + params['first'] = first + params['skip'] = skip + + response = graph_client.execute(query, params) + page_res = list(response.values())[0] + + skip += first + res.extend(page_res) + return res + + def graph_get_leverage_positions( borrow_ltv: float, block_number: BlockNumber ) -> list[LeveragePosition]: query = gql( """ - query PositionsQuery($block: Int, $borrowLTV: String) { + query PositionsQuery($borrowLTV: String, $block: Int, $first: Int, $skip: Int) { leverageStrategyPositions( block: { number: $block }, where: { borrowLtv_gt: $borrowLTV }, orderBy: borrowLtv, - orderDirection: desc + orderDirection: desc, + first: $first, + skip: $skip ) { user proxy @@ -36,9 +61,9 @@ def graph_get_leverage_positions( """ ) params = {'block': block_number, 'borrowLTV': str(borrow_ltv)} - response = graph_client.execute(query, params) + response = paginate_query(query, params) result = [] - for data in response['leverageStrategyPositions']: # pylint: disable=unsubscriptable-object + for data in response: position = LeveragePosition( vault=Web3.to_checksum_address(data['vault']['id']), user=Web3.to_checksum_address(data['user']), @@ -65,12 +90,20 @@ def graph_get_allocators( ) -> list[ChecksumAddress]: query = gql( """ - query AllocatorsQuery($ltv: String, $addresses: [String], $block: Int) { + query AllocatorsQuery( + $ltv: String, + $addresses: [String], + $block: Int, + $first: Int, + $skip: Int + ) { allocators( block: { number: $block }, where: { ltv_gt: $ltv, address_in: $addresses }, orderBy: ltv, - orderDirection: desc + orderDirection: desc, + first: $first, + skip: $skip ) { address } @@ -82,9 +115,9 @@ def graph_get_allocators( 'addresses': [address.lower() for address in addresses], 'block': block_number, } - response = graph_client.execute(query, params) + response = paginate_query(query, params) result = [] - for data in response['allocators']: # pylint: disable=unsubscriptable-object + for data in response: result.append( Web3.to_checksum_address(data['address']), ) @@ -94,10 +127,12 @@ def graph_get_allocators( def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsTokenExitRequest]: query = gql( """ - query ExitRequestsQuery($ltv: String, $block: Int) { + query ExitRequestsQuery($ltv: String, $block: Int, $first: Int, $skip: Int) { osTokenExitRequests( block: { number: $block }, where: {ltv_gt: $ltv} + first: $first + skip: $skip ) { owner ltv @@ -111,9 +146,9 @@ def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsT """ ) params = {'ltv': str(ltv), 'block': block_number} - response = graph_client.execute(query, params) + response = paginate_query(query, params) result = [] - for data in response['osTokenExitRequests']: # pylint: disable=unsubscriptable-object + for data in response: result.append( OsTokenExitRequest( vault=Web3.to_checksum_address(data['vault']['id']), From c08257124acc0a3de9b0b9d041a59314f431e7d8 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Mon, 9 Dec 2024 22:32:44 +0300 Subject: [PATCH 10/19] Separate execution module --- src/exit/execution.py | 161 +++++++++++++++++++++++++ src/exit/tasks.py | 269 +++++++++++------------------------------- 2 files changed, 229 insertions(+), 201 deletions(-) create mode 100644 src/exit/execution.py diff --git a/src/exit/execution.py b/src/exit/execution.py new file mode 100644 index 0000000..daa16e1 --- /dev/null +++ b/src/exit/execution.py @@ -0,0 +1,161 @@ +import logging +from typing import cast + +from eth_typing import ChecksumAddress, HexStr +from web3 import Web3 +from web3.types import BlockNumber + +from src.common.settings import EXECUTION_TRANSACTION_TIMEOUT +from src.common.typings import HarvestParams + +from .clients import execution_client +from .contracts import ( + VaultContract, + get_vault_contract, + keeper_contract, + leverage_strategy_contract, + multicall_contract, + ostoken_vault_escrow_contract, +) +from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def can_force_enter_exit_queue( + vault: ChecksumAddress, + user: ChecksumAddress, + harvest_params: HarvestParams | None, + block_number: BlockNumber, +) -> bool: + vault_contract = get_vault_contract(vault) + calls = [] + update_state_call = None + if harvest_params and keeper_contract.can_harvest(vault, block_number): + update_state_call = ( + vault, + _encode_update_state_call(vault_contract, harvest_params), + ) + calls.append(update_state_call) + + can_force_enter_exit_queue_call = leverage_strategy_contract.encode_abi( + fn_name='canForceEnterExitQueue', args=[vault, user] + ) + calls.append((leverage_strategy_contract.address, can_force_enter_exit_queue_call)) + # fetch data + _, response = multicall_contract.aggregate(calls, block_number) + if update_state_call: + response.pop(0) + return bool(Web3.to_int(response.pop(0))) + + +def vault_claim_exited_assets( + position: LeveragePosition, + harvest_params: HarvestParams | None, + block_number: BlockNumber, +) -> HexStr | None: + vault = position.vault + vault_contract = get_vault_contract(vault) + calls = [] + if harvest_params and keeper_contract.can_harvest(vault, block_number): + update_state_call = ( + vault, + _encode_update_state_call(vault_contract, harvest_params), + ) + calls.append(update_state_call) + + exit_request = position.exit_request + exit_request = cast(ExitRequest, exit_request) + claim_call = vault_contract.encode_abi( + fn_name='claimExitedAssets', + args=[exit_request.position_ticket, exit_request.timestamp, exit_request.exit_queue_index], + ) + calls.append((vault_contract.address, claim_call)) + try: + tx = multicall_contract.functions.aggregate(calls).transact() + except Exception as e: + logger.error( + 'Failed to claim exited assets; vault=%s, user=%s %s: ', vault, position.user, e + ) + logger.exception(e) + return None + + tx_hash = Web3.to_hex(tx) + logger.info('Waiting for transaction %s confirmation', tx_hash) + tx_receipt = execution_client.eth.wait_for_transaction_receipt( + tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + ) + if not tx_receipt['status']: + logger.error('Exited assets claim transaction failed') + return None + + return tx_hash + + +def os_token_claim_exited_assets(exit_request: OsTokenExitRequest) -> HexStr | None: + try: + tx = ostoken_vault_escrow_contract.claim_exited_assets( + vault=exit_request.vault, + exit_position_ticket=exit_request.position_ticket, + os_token_shares=exit_request.os_token_shares, + ) + except Exception as e: + logger.error( + 'Failed to claim ostoken exited assets; vault=%s, position_ticket=%s %s: ', + exit_request.vault, + exit_request.position_ticket, + e, + ) + logger.exception(e) + return None + + tx_hash = Web3.to_hex(tx) + logger.info('Waiting for transaction %s confirmation', tx_hash) + tx_receipt = execution_client.eth.wait_for_transaction_receipt( + tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + ) + if not tx_receipt['status']: + logger.error('Exited assets claim transaction failed') + return None + + return tx_hash + + +def force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: + try: + tx = leverage_strategy_contract.force_enter_exit_queue( + vault=vault, + user=user, + ) + except Exception as e: + logger.error('Failed to force enter exit queue; vault=%s, user=%s %s: ', vault, user, e) + logger.exception(e) + return None + + tx_hash = Web3.to_hex(tx) + logger.info('Waiting for transaction %s confirmation', tx_hash) + tx_receipt = execution_client.eth.wait_for_transaction_receipt( + tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + ) + if not tx_receipt['status']: + logger.error('Force enter exit queue transaction failed') + return None + + return tx_hash + + +def _encode_update_state_call( + vault_contract: VaultContract, harvest_params: HarvestParams +) -> HexStr: + return vault_contract.encode_abi( + fn_name='updateState', + args=[ + ( + harvest_params.rewards_root, + harvest_params.reward, + harvest_params.unlocked_mev_reward, + harvest_params.proof, + ) + ], + ) diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 4f31f8a..2ea54d2 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -1,30 +1,29 @@ import logging -from typing import cast -from eth_typing import ChecksumAddress, HexStr -from web3 import Web3 +from eth_typing import ChecksumAddress from web3.types import BlockNumber -from src.common.settings import EXECUTION_TRANSACTION_TIMEOUT from src.common.typings import HarvestParams from src.ltv.graph import graph_get_harvest_params from .clients import execution_client from .contracts import ( - VaultContract, - get_vault_contract, - keeper_contract, leverage_strategy_contract, - multicall_contract, ostoken_vault_escrow_contract, strategy_registry_contract, ) +from .execution import ( + can_force_enter_exit_queue, + force_enter_exit_queue, + os_token_claim_exited_assets, + vault_claim_exited_assets, +) from .graph import ( graph_get_allocators, graph_get_leverage_positions, graph_ostoken_exit_requests, ) -from .typings import ExitRequest, LeveragePosition +from .typings import LeveragePosition logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -40,12 +39,12 @@ def force_exits() -> None: block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) block_number = block['number'] - handle_leverage_postions(block_number) + handle_leverage_positions(block_number) handel_exit_position(block_number) -def handle_leverage_postions(block_number: BlockNumber) -> None: - # force exit leverage positions +def handle_leverage_positions(block_number: BlockNumber) -> None: + """Process graph leverage positions.""" strategy_id = leverage_strategy_contract.strategy_id() borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD @@ -63,47 +62,11 @@ def handle_leverage_postions(block_number: BlockNumber) -> None: harvest_params = graph_get_harvest_params(position.vault) vault_to_harvest_params[position.vault] = harvest_params - if can_force_enter_exit_queue( - vault=position.vault, - user=position.user, + handle_leverage_position( + position=position, harvest_params=harvest_params, block_number=block_number, - ): - # claim active exit request - if position.exit_request and position.exit_request.can_be_claimed: - logger.info( - 'Claiming exited assets for leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - tx_hash = claim_exited_assets( - position=position, - harvest_params=harvest_params, - block_number=block_number, - ) - if tx_hash: - logger.info( - 'Successfully claimed exited assets for leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - else: - continue - logger.info( - 'Force exiting leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - tx_hash = _force_enter_exit_queue( - vault=position.vault, - user=position.user, - ) - if tx_hash: - logger.info( - 'Successfully triggered exit for leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) + ) # check by position proxy ltv proxy_to_position = {position.proxy: position for position in leverage_positions} @@ -120,50 +83,15 @@ def handle_leverage_postions(block_number: BlockNumber) -> None: harvest_params = graph_get_harvest_params(position.vault) vault_to_harvest_params[position.vault] = harvest_params - if can_force_enter_exit_queue( - vault=position.vault, - user=position.user, + handle_leverage_position( + position=position, harvest_params=harvest_params, block_number=block_number, - ): - # claim active exit request - if position.exit_request and position.exit_request.can_be_claimed: - logger.info( - 'Claiming exited assets for leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - tx_hash = claim_exited_assets( - position=position, - harvest_params=harvest_params, - block_number=block_number, - ) - if tx_hash: - logger.info( - 'Successfully claimed exited assets for leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - else: - continue - logger.info( - 'Force exiting leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) - tx_hash = _force_enter_exit_queue( - vault=position.vault, - user=position.user, - ) - if tx_hash: - logger.info( - 'Successfully exited leverage positions: vault=%s, user=%s...', - position.vault, - position.user, - ) + ) def handel_exit_position(block_number: BlockNumber) -> None: + """Process osTokenExitRequests from graph and claim exited assets.""" # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() max_ltv_percent = max_ltv_percent // 10**18 * 100 @@ -176,122 +104,61 @@ def handel_exit_position(block_number: BlockNumber) -> None: exit_request.vault, exit_request.owner, ) - ostoken_vault_escrow_contract.claim_exited_assets( - vault=exit_request.vault, - exit_position_ticket=exit_request.position_ticket, - os_token_shares=exit_request.os_token_shares, - ) - logger.info( - 'Successfully claimed exited assets: vault=%s, user=%s...', - exit_request.vault, - exit_request.owner, - ) - - -def _encode_update_state_call( - vault_contract: VaultContract, harvest_params: HarvestParams -) -> HexStr: - return vault_contract.encode_abi( - fn_name='updateState', - args=[ - ( - harvest_params.rewards_root, - harvest_params.reward, - harvest_params.unlocked_mev_reward, - harvest_params.proof, + tx_hash = os_token_claim_exited_assets(exit_request) + if tx_hash: + logger.info( + 'Successfully claimed exited assets: vault=%s, user=%s...', + exit_request.vault, + exit_request.owner, ) - ], - ) -def can_force_enter_exit_queue( - vault: ChecksumAddress, - user: ChecksumAddress, - harvest_params: HarvestParams | None, - block_number: BlockNumber, -) -> bool: - vault_contract = get_vault_contract(vault) - calls = [] - update_state_call = None - if harvest_params and keeper_contract.can_harvest(vault, block_number): - update_state_call = ( - vault, - _encode_update_state_call(vault_contract, harvest_params), +def handle_leverage_position( + position: LeveragePosition, harvest_params: HarvestParams | None, block_number: BlockNumber +) -> None: + """ + Submit force exit for leverage postion. + Also check for position active exit request and claim assets is we can. + """ + if not can_force_enter_exit_queue( + vault=position.vault, + user=position.user, + harvest_params=harvest_params, + block_number=block_number, + ): + return + # claim active exit request + if position.exit_request and position.exit_request.can_be_claimed: + logger.info( + 'Claiming exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, ) - calls.append(update_state_call) - - can_force_enter_exit_queue_call = leverage_strategy_contract.encode_abi( - fn_name='canForceEnterExitQueue', args=[vault, user] - ) - calls.append((leverage_strategy_contract.address, can_force_enter_exit_queue_call)) - # fetch data - _, response = multicall_contract.aggregate(calls, block_number) - if update_state_call: - response.pop(0) - return bool(Web3.to_int(response.pop(0))) - - -def claim_exited_assets( - position: LeveragePosition, - harvest_params: HarvestParams | None, - block_number: BlockNumber, -) -> HexStr | None: - vault = position.vault - vault_contract = get_vault_contract(vault) - calls = [] - if harvest_params and keeper_contract.can_harvest(vault, block_number): - update_state_call = ( - vault, - _encode_update_state_call(vault_contract, harvest_params), + tx_hash = vault_claim_exited_assets( + position=position, + harvest_params=harvest_params, + block_number=block_number, ) - calls.append(update_state_call) - - exit_request = position.exit_request - exit_request = cast(ExitRequest, exit_request) - claim_call = vault_contract.encode_abi( - fn_name='claimExitedAssets', - args=[exit_request.position_ticket, exit_request.timestamp, exit_request.exit_queue_index], + if tx_hash: + logger.info( + 'Successfully claimed exited assets for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, + ) + else: + return + logger.info( + 'Force exiting leverage positions: vault=%s, user=%s...', + position.vault, + position.user, ) - calls.append((vault_contract.address, claim_call)) - try: - tx = multicall_contract.functions.aggregate(calls).transact() - except Exception as e: - logger.error( - 'Failed to claim exited assets; vault=%s, user=%s %s: ', vault, position.user, e - ) - logger.exception(e) - return None - - tx_hash = Web3.to_hex(tx) - logger.info('Waiting for transaction %s confirmation', tx_hash) - tx_receipt = execution_client.eth.wait_for_transaction_receipt( - tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + tx_hash = force_enter_exit_queue( + vault=position.vault, + user=position.user, ) - if not tx_receipt['status']: - logger.error('Exited assets claim transaction failed') - return None - - return tx_hash - - -def _force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: - try: - tx = leverage_strategy_contract.force_enter_exit_queue( - vault=vault, - user=user, + if tx_hash: + logger.info( + 'Successfully triggered exit for leverage positions: vault=%s, user=%s...', + position.vault, + position.user, ) - except Exception as e: - logger.error('Failed to force enter exit queue; vault=%s, user=%s %s: ', vault, user, e) - logger.exception(e) - return None - - tx_hash = Web3.to_hex(tx) - logger.info('Waiting for transaction %s confirmation', tx_hash) - tx_receipt = execution_client.eth.wait_for_transaction_receipt( - tx, timeout=EXECUTION_TRANSACTION_TIMEOUT - ) - if not tx_receipt['status']: - logger.error('Force enter exit queue transaction failed') - return None - - return tx_hash From b6d4bd2fad12576b58f5978ac0f036e7810895b8 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Mon, 9 Dec 2024 23:58:51 +0300 Subject: [PATCH 11/19] Small cleanup --- src/exit/contracts.py | 4 +--- src/exit/execution.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/exit/contracts.py b/src/exit/contracts.py index bba281a..af9ebd6 100644 --- a/src/exit/contracts.py +++ b/src/exit/contracts.py @@ -66,9 +66,7 @@ class VaultContract(ContractWrapper): class KeeperContract(ContractWrapper): - abi_path = 'abi/IKeeper.json' - - def can_harvest(self, vault: ChecksumAddress, block_number: BlockNumber | None = None) -> bool: + def can_harvest(self, vault: ChecksumAddress, block_number: BlockNumber) -> bool: return self.contract.functions.canHarvest(vault).call(block_identifier=block_number) diff --git a/src/exit/execution.py b/src/exit/execution.py index daa16e1..de606fa 100644 --- a/src/exit/execution.py +++ b/src/exit/execution.py @@ -43,7 +43,6 @@ def can_force_enter_exit_queue( fn_name='canForceEnterExitQueue', args=[vault, user] ) calls.append((leverage_strategy_contract.address, can_force_enter_exit_queue_call)) - # fetch data _, response = multicall_contract.aggregate(calls, block_number) if update_state_call: response.pop(0) From 066d1ee27ea41fbd5d1e12c581f759fe84f225d3 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Tue, 10 Dec 2024 17:18:59 +0300 Subject: [PATCH 12/19] Use leverage position contract for claim and state update --- src/exit/abi/IEthVault.json | 1288 ----------------------------------- src/exit/contracts.py | 37 +- src/exit/execution.py | 103 ++- src/exit/graph.py | 56 +- src/exit/tasks.py | 46 +- src/exit/typings.py | 22 +- 6 files changed, 139 insertions(+), 1413 deletions(-) delete mode 100644 src/exit/abi/IEthVault.json diff --git a/src/exit/abi/IEthVault.json b/src/exit/abi/IEthVault.json deleted file mode 100644 index 735e479..0000000 --- a/src/exit/abi/IEthVault.json +++ /dev/null @@ -1,1288 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "name": "CheckpointCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "Deposited", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "ExitQueueEntered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "prevPositionTicket", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newPositionTicket", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "withdrawnAssets", - "type": "uint256" - } - ], - "name": "ExitedAssetsClaimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "penalty", - "type": "uint256" - } - ], - "name": "ExitingAssetsPenalized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "feeRecipient", - "type": "address" - } - ], - "name": "FeeRecipientUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "name": "FeeSharesMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "keysManager", - "type": "address" - } - ], - "name": "KeysManagerUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "metadataIpfsHash", - "type": "string" - } - ], - "name": "MetadataUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "OsTokenBurned", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "receivedAssets", - "type": "uint256" - } - ], - "name": "OsTokenLiquidated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "OsTokenMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "name": "OsTokenRedeemed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "Redeemed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "name": "V2ExitQueueEntered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - } - ], - "name": "ValidatorRegistered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "validatorsManager", - "type": "address" - } - ], - "name": "ValidatorsManagerUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "caller", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "validatorsRoot", - "type": "bytes32" - } - ], - "name": "ValidatorsRootUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "admin", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint128", - "name": "osTokenShares", - "type": "uint128" - } - ], - "name": "burnOsToken", - "outputs": [ - { - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "exitQueueIndex", - "type": "uint256" - } - ], - "name": "calculateExitedAssets", - "outputs": [ - { - "internalType": "uint256", - "name": "leftTickets", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "exitedTickets", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "exitedAssets", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "capacity", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "exitQueueIndex", - "type": "uint256" - } - ], - "name": "claimExitedAssets", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "name": "convertToAssets", - "outputs": [ - { - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "name": "convertToShares", - "outputs": [ - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "depositAndMintOsToken", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "enterExitQueue", - "outputs": [ - { - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "feePercent", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "feeRecipient", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "positionTicket", - "type": "uint256" - } - ], - "name": "getExitQueueIndex", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "getShares", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "params", - "type": "bytes" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "isStateUpdateRequired", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "liquidateOsToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "mevEscrow", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "mintOsToken", - "outputs": [ - { - "internalType": "uint256", - "name": "assets", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "data", - "type": "bytes[]" - } - ], - "name": "multicall", - "outputs": [ - { - "internalType": "bytes[]", - "name": "results", - "type": "bytes[]" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "user", - "type": "address" - } - ], - "name": "osTokenPositions", - "outputs": [ - { - "internalType": "uint128", - "name": "shares", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proxiableUUID", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "queuedShares", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "receiveFromMevEscrow", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "redeemOsToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "validatorsRegistryRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "validators", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signatures", - "type": "bytes" - }, - { - "internalType": "string", - "name": "exitSignaturesIpfsHash", - "type": "string" - } - ], - "internalType": "struct IKeeperValidators.ApprovalParams", - "name": "keeperParams", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "validatorsManagerSignature", - "type": "bytes" - } - ], - "name": "registerValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_feeRecipient", - "type": "address" - } - ], - "name": "setFeeRecipient", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "metadataIpfsHash", - "type": "string" - } - ], - "name": "setMetadata", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_validatorsManager", - "type": "address" - } - ], - "name": "setValidatorsManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "totalAssets", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalExitingAssets", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalShares", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "rewardsRoot", - "type": "bytes32" - }, - { - "internalType": "int160", - "name": "reward", - "type": "int160" - }, - { - "internalType": "uint160", - "name": "unlockedMevReward", - "type": "uint160" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "internalType": "struct IKeeperRewards.HarvestParams", - "name": "harvestParams", - "type": "tuple" - } - ], - "name": "updateState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "address", - "name": "referrer", - "type": "address" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "rewardsRoot", - "type": "bytes32" - }, - { - "internalType": "int160", - "name": "reward", - "type": "int160" - }, - { - "internalType": "uint160", - "name": "unlockedMevReward", - "type": "uint160" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "internalType": "struct IKeeperRewards.HarvestParams", - "name": "harvestParams", - "type": "tuple" - } - ], - "name": "updateStateAndDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "uint256", - "name": "osTokenShares", - "type": "uint256" - }, - { - "internalType": "address", - "name": "referrer", - "type": "address" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "rewardsRoot", - "type": "bytes32" - }, - { - "internalType": "int160", - "name": "reward", - "type": "int160" - }, - { - "internalType": "uint160", - "name": "unlockedMevReward", - "type": "uint160" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "internalType": "struct IKeeperRewards.HarvestParams", - "name": "harvestParams", - "type": "tuple" - } - ], - "name": "updateStateAndDepositAndMintOsToken", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "vaultId", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "withdrawableAssets", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/src/exit/contracts.py b/src/exit/contracts.py index af9ebd6..1ac98f2 100644 --- a/src/exit/contracts.py +++ b/src/exit/contracts.py @@ -1,9 +1,8 @@ import logging from eth_typing import ChecksumAddress -from hexbytes import HexBytes from web3 import Web3 -from web3.types import BlockNumber, HexStr, Wei +from web3.types import BlockNumber, HexStr from src.common.contracts import ContractWrapper from src.common.settings import network_config @@ -19,33 +18,11 @@ class LeverageStrategyContract(ContractWrapper): def strategy_id(self) -> str: return self.contract.functions.strategyId().call() - def force_enter_exit_queue( - self, - vault: ChecksumAddress, - user: ChecksumAddress, - ) -> HexBytes: - return self.contract.functions.forceEnterExitQueue( - vault, - user, - ).transact() - class OsTokenVaultEscrowContract(ContractWrapper): def liq_threshold_percent(self) -> int: return self.contract.functions.liqThresholdPercent().call() - def claim_exited_assets( - self, - vault: ChecksumAddress, - exit_position_ticket: int, - os_token_shares: Wei, - ) -> HexBytes: - return self.contract.functions.claimExitedAssets( - vault, - exit_position_ticket, - os_token_shares, - ).transact() - class StrategiesRegistryContract(ContractWrapper): def get_vault_ltv_percent(self, strategy_id: str) -> int: @@ -61,10 +38,6 @@ def get_borrow_ltv_percent(self, strategy_id: str) -> int: return Web3.to_int(value) -class VaultContract(ContractWrapper): - ... - - class KeeperContract(ContractWrapper): def can_harvest(self, vault: ChecksumAddress, block_number: BlockNumber) -> bool: return self.contract.functions.canHarvest(vault).call(block_identifier=block_number) @@ -108,11 +81,3 @@ def aggregate( address=network_config.MULTICALL_CONTRACT_ADDRESS, client=execution_client, ) - - -def get_vault_contract(address: ChecksumAddress) -> VaultContract: - return VaultContract( - abi_path=f'{ABI_DIR}/IEthVault.json', - address=address, - client=execution_client, - ) diff --git a/src/exit/execution.py b/src/exit/execution.py index de606fa..2e843c9 100644 --- a/src/exit/execution.py +++ b/src/exit/execution.py @@ -1,5 +1,4 @@ import logging -from typing import cast from eth_typing import ChecksumAddress, HexStr from web3 import Web3 @@ -9,15 +8,8 @@ from src.common.typings import HarvestParams from .clients import execution_client -from .contracts import ( - VaultContract, - get_vault_contract, - keeper_contract, - leverage_strategy_contract, - multicall_contract, - ostoken_vault_escrow_contract, -) -from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest +from .contracts import keeper_contract, leverage_strategy_contract, multicall_contract +from .typings import ExitRequest logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -29,13 +21,12 @@ def can_force_enter_exit_queue( harvest_params: HarvestParams | None, block_number: BlockNumber, ) -> bool: - vault_contract = get_vault_contract(vault) calls = [] update_state_call = None if harvest_params and keeper_contract.can_harvest(vault, block_number): update_state_call = ( - vault, - _encode_update_state_call(vault_contract, harvest_params), + leverage_strategy_contract.address, + _encode_update_state_call(vault, harvest_params), ) calls.append(update_state_call) @@ -49,33 +40,38 @@ def can_force_enter_exit_queue( return bool(Web3.to_int(response.pop(0))) -def vault_claim_exited_assets( - position: LeveragePosition, +def claim_exited_assets( + vault: ChecksumAddress, + user: ChecksumAddress, + exit_request: ExitRequest, harvest_params: HarvestParams | None, block_number: BlockNumber, ) -> HexStr | None: - vault = position.vault - vault_contract = get_vault_contract(vault) calls = [] if harvest_params and keeper_contract.can_harvest(vault, block_number): update_state_call = ( - vault, - _encode_update_state_call(vault_contract, harvest_params), + leverage_strategy_contract.address, + _encode_update_state_call(vault, harvest_params), ) calls.append(update_state_call) - exit_request = position.exit_request - exit_request = cast(ExitRequest, exit_request) - claim_call = vault_contract.encode_abi( + claim_call = leverage_strategy_contract.encode_abi( fn_name='claimExitedAssets', - args=[exit_request.position_ticket, exit_request.timestamp, exit_request.exit_queue_index], + args=[ + vault, + user, + (exit_request.position_ticket, exit_request.timestamp, exit_request.exit_queue_index), + ], ) - calls.append((vault_contract.address, claim_call)) + calls.append((leverage_strategy_contract.address, claim_call)) try: tx = multicall_contract.functions.aggregate(calls).transact() except Exception as e: logger.error( - 'Failed to claim exited assets; vault=%s, user=%s %s: ', vault, position.user, e + 'Failed to claim exited assets; vault=%s, position_ticket=%s %s: ', + vault, + exit_request.position_ticket, + e, ) logger.exception(e) return None @@ -92,41 +88,27 @@ def vault_claim_exited_assets( return tx_hash -def os_token_claim_exited_assets(exit_request: OsTokenExitRequest) -> HexStr | None: - try: - tx = ostoken_vault_escrow_contract.claim_exited_assets( - vault=exit_request.vault, - exit_position_ticket=exit_request.position_ticket, - os_token_shares=exit_request.os_token_shares, - ) - except Exception as e: - logger.error( - 'Failed to claim ostoken exited assets; vault=%s, position_ticket=%s %s: ', - exit_request.vault, - exit_request.position_ticket, - e, +def force_enter_exit_queue( + vault: ChecksumAddress, + user: ChecksumAddress, + harvest_params: HarvestParams | None, + block_number: BlockNumber, +) -> HexStr | None: + calls = [] + if harvest_params and keeper_contract.can_harvest(vault, block_number): + update_state_call = ( + leverage_strategy_contract.address, + _encode_update_state_call(vault, harvest_params), ) - logger.exception(e) - return None + calls.append(update_state_call) - tx_hash = Web3.to_hex(tx) - logger.info('Waiting for transaction %s confirmation', tx_hash) - tx_receipt = execution_client.eth.wait_for_transaction_receipt( - tx, timeout=EXECUTION_TRANSACTION_TIMEOUT + force_enter_call = leverage_strategy_contract.encode_abi( + fn_name='forceEnterExitQueue', + args=[vault, user], ) - if not tx_receipt['status']: - logger.error('Exited assets claim transaction failed') - return None - - return tx_hash - - -def force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> HexStr | None: + calls.append((leverage_strategy_contract.address, force_enter_call)) try: - tx = leverage_strategy_contract.force_enter_exit_queue( - vault=vault, - user=user, - ) + tx = multicall_contract.functions.aggregate(calls).transact() except Exception as e: logger.error('Failed to force enter exit queue; vault=%s, user=%s %s: ', vault, user, e) logger.exception(e) @@ -145,16 +127,17 @@ def force_enter_exit_queue(vault: ChecksumAddress, user: ChecksumAddress) -> Hex def _encode_update_state_call( - vault_contract: VaultContract, harvest_params: HarvestParams + vault_address: ChecksumAddress, harvest_params: HarvestParams ) -> HexStr: - return vault_contract.encode_abi( - fn_name='updateState', + return leverage_strategy_contract.encode_abi( + fn_name='updateVaultState', args=[ + vault_address, ( harvest_params.rewards_root, harvest_params.reward, harvest_params.unlocked_mev_reward, harvest_params.proof, - ) + ), ], ) diff --git a/src/exit/graph.py b/src/exit/graph.py index b416050..7334c9a 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -3,7 +3,7 @@ from gql import gql from graphql import DocumentNode from web3 import Web3 -from web3.types import BlockNumber, ChecksumAddress, Wei +from web3.types import BlockNumber, ChecksumAddress from src.common.settings import GRAPH_PAGE_SIZE @@ -49,6 +49,7 @@ def graph_get_leverage_positions( id } exitRequest { + id positionTicket timestamp exitQueueIndex @@ -70,16 +71,7 @@ def graph_get_leverage_positions( proxy=Web3.to_checksum_address(data['proxy']), ) if data['exitRequest']: - request_data = data['exitRequest'] - exit_request = ExitRequest( - position_ticket=request_data['positionTicket'], - timestamp=request_data['timestamp'], - exit_queue_index=request_data['exitQueueIndex'], - is_claimable=request_data['isClaimable'], - exited_assets=request_data['exitedAssets'], - total_assets=request_data['totalAssets'], - ) - position.exit_request = exit_request + position.exit_request = ExitRequest.from_graph(data['exitRequest']) result.append(position) return result @@ -134,6 +126,7 @@ def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsT first: $first skip: $skip ) { + id owner ltv positionTicket @@ -147,15 +140,52 @@ def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsT ) params = {'ltv': str(ltv), 'block': block_number} response = paginate_query(query, params) + + exit_requests = graph_get_exit_requests( + ids=[item['id'] for item in response], block_number=block_number + ) + id_to_exit_request = {exit.id: exit for exit in exit_requests} + result = [] for data in response: result.append( OsTokenExitRequest( + id=data['id'], vault=Web3.to_checksum_address(data['vault']['id']), owner=Web3.to_checksum_address(data['owner']), - os_token_shares=Wei(int(data['osTokenShares'])), ltv=data['ltv'], - position_ticket=int(data['positionTicket']), + exit_request=id_to_exit_request[data['id']], ) ) + + return result + + +def graph_get_exit_requests(ids: list[str], block_number: BlockNumber) -> list[ExitRequest]: + query = gql( + """ + query exitRequestQuery($ids: [String], $block: Int, $first: Int, $skip: Int) { + exitRequests( + block: { number: $block }, + where: { id_in: $ids }, + orderBy: id, + first: $first, + skip: $skip + ) { + id + positionTicket + timestamp + exitQueueIndex + isClaimable + exitedAssets + totalAssets + } + } + """ + ) + params = {'block': block_number, 'ids': ids} + response = paginate_query(query, params) + result = [] + for data in response: + result.append(ExitRequest.from_graph(data)) return result diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 2ea54d2..5fdf17f 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -14,9 +14,8 @@ ) from .execution import ( can_force_enter_exit_queue, + claim_exited_assets, force_enter_exit_queue, - os_token_claim_exited_assets, - vault_claim_exited_assets, ) from .graph import ( graph_get_allocators, @@ -48,7 +47,6 @@ def handle_leverage_positions(block_number: BlockNumber) -> None: strategy_id = leverage_strategy_contract.strategy_id() borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD - leverage_positions = graph_get_leverage_positions( borrow_ltv=borrow_ltv, block_number=block_number ) @@ -94,22 +92,39 @@ def handel_exit_position(block_number: BlockNumber) -> None: """Process osTokenExitRequests from graph and claim exited assets.""" # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() - max_ltv_percent = max_ltv_percent // 10**18 * 100 + max_ltv_percent = max_ltv_percent // WAD exit_requests = graph_ostoken_exit_requests(max_ltv_percent, block_number=block_number) + exit_requests = [ + exit_request for exit_request in exit_requests if exit_request.exit_request.can_be_claimed + ] + logger.info('Force assets claim for %d exit requests...', len(exit_requests)) + vault_to_harvest_params: dict[ChecksumAddress, HarvestParams | None] = {} + + for os_token_exit_request in exit_requests: + vault = os_token_exit_request.vault + harvest_params = vault_to_harvest_params.get(vault) + if not harvest_params: + harvest_params = graph_get_harvest_params(vault) + vault_to_harvest_params[vault] = harvest_params - for exit_request in exit_requests: logger.info( 'Claiming exited assets: vault=%s, user=%s...', - exit_request.vault, - exit_request.owner, + vault, + os_token_exit_request.owner, + ) + tx_hash = claim_exited_assets( + vault=vault, + user=os_token_exit_request.owner, + exit_request=os_token_exit_request.exit_request, + harvest_params=harvest_params, + block_number=block_number, ) - tx_hash = os_token_claim_exited_assets(exit_request) if tx_hash: logger.info( 'Successfully claimed exited assets: vault=%s, user=%s...', - exit_request.vault, - exit_request.owner, + vault, + os_token_exit_request.owner, ) @@ -118,7 +133,7 @@ def handle_leverage_position( ) -> None: """ Submit force exit for leverage postion. - Also check for position active exit request and claim assets is we can. + Also check for position active exit request and claim assets is possible. """ if not can_force_enter_exit_queue( vault=position.vault, @@ -127,6 +142,7 @@ def handle_leverage_position( block_number=block_number, ): return + # claim active exit request if position.exit_request and position.exit_request.can_be_claimed: logger.info( @@ -134,8 +150,10 @@ def handle_leverage_position( position.vault, position.user, ) - tx_hash = vault_claim_exited_assets( - position=position, + tx_hash = claim_exited_assets( + vault=position.vault, + user=position.user, + exit_request=position.exit_request, harvest_params=harvest_params, block_number=block_number, ) @@ -155,6 +173,8 @@ def handle_leverage_position( tx_hash = force_enter_exit_queue( vault=position.vault, user=position.user, + harvest_params=harvest_params, + block_number=block_number, ) if tx_hash: logger.info( diff --git a/src/exit/typings.py b/src/exit/typings.py index 4862c6b..6d478f9 100644 --- a/src/exit/typings.py +++ b/src/exit/typings.py @@ -5,9 +5,10 @@ @dataclass class ExitRequest: + id: str position_ticket: int timestamp: int - exit_queue_index: int + exit_queue_index: int | None is_claimable: bool exited_assets: Wei total_assets: Wei @@ -16,6 +17,21 @@ class ExitRequest: def can_be_claimed(self) -> bool: return self.is_claimable and self.exited_assets == self.total_assets + @staticmethod + def from_graph(data: dict) -> 'ExitRequest': + exit_queue_index = ( + int(data['exitQueueIndex']) if data.get('exitQueueIndex') is not None else None + ) + return ExitRequest( + id=data['id'], + position_ticket=int(data['positionTicket']), + timestamp=int(data['timestamp']), + exit_queue_index=exit_queue_index, + is_claimable=data['isClaimable'], + exited_assets=Wei(int(data['exitedAssets'])), + total_assets=Wei(int(data['totalAssets'])), + ) + @dataclass class LeveragePosition: @@ -27,8 +43,8 @@ class LeveragePosition: @dataclass class OsTokenExitRequest: + id: str vault: ChecksumAddress owner: ChecksumAddress - position_ticket: int - os_token_shares: Wei ltv: int + exit_request: ExitRequest From ac568002bba047db16ee318d134865026fd98088 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Tue, 10 Dec 2024 18:39:52 +0300 Subject: [PATCH 13/19] Bump dockerfile base image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index eb7a762..9fea9c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # `python-base` sets up all our shared environment variables -FROM python:3.10.14-alpine3.20 as python-base +FROM python:3.10.16-alpine3.21 as python-base # python ENV PYTHONUNBUFFERED=1 \ From 1b4a2667b0577b77e71e8646c0ef3df750714623 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 19 Dec 2024 21:59:45 +0300 Subject: [PATCH 14/19] Use sw-utils graph client --- poetry.lock | 273 +++++++++++++++++++++--------------------- pyproject.toml | 3 +- src/common/graph.py | 48 ++++++++ src/exit/clients.py | 23 ++-- src/exit/execution.py | 13 +- src/exit/graph.py | 49 +++----- src/exit/settings.py | 1 + src/exit/tasks.py | 47 ++++++-- src/force_exit.py | 8 +- src/ltv/clients.py | 22 ++-- src/ltv/graph.py | 44 ++----- src/ltv/settings.py | 1 + src/ltv/tasks.py | 12 +- src/update_ltv.py | 8 +- 14 files changed, 294 insertions(+), 258 deletions(-) create mode 100644 src/common/graph.py diff --git a/poetry.lock b/poetry.lock index feb809a..2d2785c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -125,13 +125,13 @@ speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, ] [package.dependencies] @@ -139,24 +139,24 @@ frozenlist = ">=1.1.0" [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, - {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, + {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, + {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -186,19 +186,19 @@ files = [ [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -454,13 +454,13 @@ files = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -709,97 +709,111 @@ files = [ [[package]] name = "cytoolz" -version = "1.0.0" +version = "1.0.1" description = "Cython implementation of Toolz: High performance functional utilities" optional = false python-versions = ">=3.8" files = [ - {file = "cytoolz-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ecf5a887acb8f079ab1b81612b1c889bcbe6611aa7804fd2df46ed310aa5a345"}, - {file = "cytoolz-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0ef30c1e091d4d59d14d8108a16d50bd227be5d52a47da891da5019ac2f8e4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7df2dfd679f0517a96ced1cdd22f5c6c6aeeed28d928a82a02bf4c3fd6fd7ac4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c51452c938e610f57551aa96e34924169c9100c0448bac88c2fb395cbd3538c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6433f03910c5e5345d82d6299457c26bf33821224ebb837c6b09d9cdbc414a6c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:389ec328bb535f09e71dfe658bf0041f17194ca4cedaacd39bafe7893497a819"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c64658e1209517ce4b54c1c9269a508b289d8d55fc742760e4b8579eacf09a33"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6039a9bd5bb988762458b9ca82b39e60ca5e5baae2ba93913990dcc5d19fa88"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85c9c8c4465ed1b2c8d67003809aec9627b129cb531d2f6cf0bbfe39952e7e4d"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:49375aad431d76650f94877afb92f09f58b6ff9055079ef4f2cd55313f5a1b39"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4c45106171c824a61e755355520b646cb35a1987b34bbf5789443823ee137f63"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3b319a7f0fed5db07d189db4046162ebc183c108df3562a65ba6ebe862d1f634"}, - {file = "cytoolz-1.0.0-cp310-cp310-win32.whl", hash = "sha256:9770e1b09748ad0d751853d994991e2592a9f8c464a87014365f80dac2e83faa"}, - {file = "cytoolz-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:20194dd02954c00c1f0755e636be75a20781f91a4ac9270c7f747e82d3c7f5a5"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dffc22fd2c91be64dbdbc462d0786f8e8ac9a275cfa1869a1084d1867d4f67e0"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a99e7e29274e293f4ffe20e07f76c2ac753a78f1b40c1828dfc54b2981b2f6c4"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c507a3e0a45c41d66b43f96797290d75d1e7a8549aa03a4a6b8854fdf3f7b8d8"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:643a593ec272ef7429099e1182a22f64ec2696c00d295d2a5be390db1b7ff176"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ce38e2e42cbae30446190c59b92a8a9029e1806fd79eaf88f48b0fe33003893"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810a6a168b8c5ecb412fbae3dd6f7ed6c6253a63caf4174ee9794ebd29b2224f"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ce8a2a85c0741c1b19b16e6782c4a5abc54c3caecda66793447112ab2fa9884"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ea4ac72e6b830861035c4c7999af8e55813f57c6d1913a3d93cc4a6babc27bf7"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a09cdfb21dfb38aa04df43e7546a41f673377eb5485da88ceb784e327ec7603b"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:658dd85deb375ff7af990a674e5c9058cef1c9d1f5dc89bc87b77be499348144"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9715d1ff5576919d10b68f17241375f6a1eec8961c25b78a83e6ef1487053f39"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f370a1f1f1afc5c1c8cc5edc1cfe0ba444263a0772af7ce094be8e734f41769d"}, - {file = "cytoolz-1.0.0-cp311-cp311-win32.whl", hash = "sha256:dbb2ec1177dca700f3db2127e572da20de280c214fc587b2a11c717fc421af56"}, - {file = "cytoolz-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:0983eee73df86e54bb4a79fcc4996aa8b8368fdbf43897f02f9c3bf39c4dc4fb"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:10e3986066dc379e30e225b230754d9f5996aa8d84c2accc69c473c21d261e46"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:16576f1bb143ee2cb9f719fcc4b845879fb121f9075c7c5e8a5ff4854bd02fc6"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3faa25a1840b984315e8b3ae517312375f4273ffc9a2f035f548b7f916884f37"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781fce70a277b20fd95dc66811d1a97bb07b611ceea9bda8b7dd3c6a4b05d59a"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a562c25338eb24d419d1e80a7ae12133844ce6fdeb4ab54459daf250088a1b2"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f29d8330aaf070304f7cd5cb7e73e198753624eb0aec278557cccd460c699b5b"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98a96c54aa55ed9c7cdb23c2f0df39a7b4ee518ac54888480b5bdb5ef69c7ef0"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:287d6d7f475882c2ddcbedf8da9a9b37d85b77690779a2d1cdceb5ae3998d52e"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:05a871688df749b982839239fcd3f8ec3b3b4853775d575ff9cd335fa7c75035"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:28bb88e1e2f7d6d4b8e0890b06d292c568984d717de3e8381f2ca1dd12af6470"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:576a4f1fc73d8836b10458b583f915849da6e4f7914f4ecb623ad95c2508cad5"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:509ed3799c47e4ada14f63e41e8f540ac6e2dab97d5d7298934e6abb9d3830ec"}, - {file = "cytoolz-1.0.0-cp312-cp312-win32.whl", hash = "sha256:9ce25f02b910630f6dc2540dd1e26c9326027ddde6c59f8cab07c56acc70714c"}, - {file = "cytoolz-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e53cfcce87e05b7f0ae2fb2b3e5820048cd0bb7b701e92bd8f75c9fbb7c9ae9"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7d56569dfe67a39ce74ffff0dc12cf0a3d1aae709667a303fe8f2dd5fd004fdf"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:035c8bb4706dcf93a89fb35feadff67e9301935bf6bb864cd2366923b69d9a29"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27c684799708bdc7ee7acfaf464836e1b4dec0996815c1d5efd6a92a4356a562"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44ab57cfc922b15d94899f980d76759ef9e0256912dfab70bf2561bea9cd5b19"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:478af5ecc066da093d7660b23d0b465a7f44179739937afbded8af00af412eb6"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da1f82a7828a42468ea2820a25b6e56461361390c29dcd4d68beccfa1b71066b"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c371b3114d38ee717780b239179e88d5d358fe759a00dcf07691b8922bbc762"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90b343b2f3b3e77c3832ba19b0b17e95412a5b2e715b05c23a55ba525d1fca49"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89a554a9ba112403232a54e15e46ff218b33020f3f45c4baf6520ab198b7ad93"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0d603f5e2b1072166745ecdd81384a75757a96a704a5642231eb51969f919d5f"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:122ef2425bd3c0419e6e5260d0b18cd25cf74de589cd0184e4a63b24a4641e2e"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8819f1f97ebe36efcaf4b550e21677c46ac8a41bed482cf66845f377dd20700d"}, - {file = "cytoolz-1.0.0-cp38-cp38-win32.whl", hash = "sha256:fcddbb853770dd6e270d89ea8742f0aa42c255a274b9e1620eb04e019b79785e"}, - {file = "cytoolz-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ca526905a014a38cc23ae78635dc51d0462c5c24425b22c08beed9ff2ee03845"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05df5ff1cdd198fb57e7368623662578c950be0b14883cadfb9ee4098415e1e5"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04a84778f48ebddb26948971dc60948907c876ba33b13f9cbb014fe65b341fc2"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f65283b618b4c4df759f57bcf8483865a73f7f268e6d76886c743407c8d26c1c"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388cd07ee9a9e504c735a0a933e53c98586a1c301a64af81f7aa7ff40c747520"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06d09e9569cfdfc5c082806d4b4582db8023a3ce034097008622bcbac7236f38"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9502bd9e37779cc9893cbab515a474c2ab6af61ed22ac2f7e16033db18fcaa85"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:364c2fda148def38003b2c86e8adde1d2aab12411dd50872c244a815262e2fda"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9b2e945617325242687189966335e785dc0fae316f4c1825baacf56e5a97e65f"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f16907fdc724c55b16776bdb7e629deae81d500fe48cfc3861231753b271355"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d3206c81ca3ba2d7b8fe78f2e116e3028e721148be753308e88dcbbc370bca52"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:becce4b13e110b5ac6b23753dcd0c977f4fdccffa31898296e13fd1109e517e3"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69a7e5e98fd446079b8b8ec5987aec9a31ec3570a6f494baefa6800b783eaf22"}, - {file = "cytoolz-1.0.0-cp39-cp39-win32.whl", hash = "sha256:b1707b6c3a91676ac83a28a231a14b337dbb4436b937e6b3e4fd44209852a48b"}, - {file = "cytoolz-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:11d48b8521ef5fe92e099f4fc00717b5d0789c3c90d5d84031b6d3b17dee1700"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e672712d5dc3094afc6fb346dd4e9c18c1f3c69608ddb8cf3b9f8428f9c26a5c"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86fb208bfb7420e1d0d20065d661310e4a8a6884851d4044f47d37ed4cd7410e"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dbe5fe3b835859fc559eb59bf2775b5a108f7f2cfab0966f3202859d787d8fd"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cace092dfda174eed09ed871793beb5b65633963bcda5b1632c73a5aceea1ce"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f7a9d816af3be9725c70efe0a6e4352a45d3877751b395014b8eb2f79d7d8d9d"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:caa7ef840847a23b379e6146760e3a22f15f445656af97e55a435c592125cfa5"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921082fff09ff6e40c12c87b49be044492b2d6bb01d47783995813b76680c7b2"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a32f1356f3b64dda883583383966948604ac69ca0b7fbcf5f28856e5f9133b4e"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af793b1738e4191d15a92e1793f1ffea9f6461022c7b2442f3cb1ea0a4f758a"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:51dfda3983fcc59075c534ce54ca041bb3c80e827ada5d4f25ff7b4049777f94"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:acfb8780c04d29423d14aaab74cd1b7b4beaba32f676e7ace02c9acfbf532aba"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99f39dcc46416dca3eb23664b73187b77fb52cd8ba2ddd8020a292d8f449db67"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d56b3721977806dcf1a68b0ecd56feb382fdb0f632af1a9fc5ab9b662b32c6"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d346620abc8c83ae634136e700432ad6202faffcc24c5ab70b87392dcda8a1"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:df0c81197fc130de94c09fc6f024a6a19c98ba8fe55c17f1e45ebba2e9229079"}, - {file = "cytoolz-1.0.0.tar.gz", hash = "sha256:eb453b30182152f9917a5189b7d99046b6ce90cdf8aeb0feff4b2683e600defd"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78"}, + {file = "cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804"}, + {file = "cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052"}, + {file = "cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead"}, + {file = "cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d"}, + {file = "cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9"}, + {file = "cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581"}, + {file = "cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9"}, + {file = "cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3a509e4ac8e711703c368476b9bbce921fcef6ebb87fa3501525f7000e44185"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7eecab6373e933dfbf4fdc0601d8fd7614f8de76793912a103b5fccf98170cd"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e55ed62087f6e3e30917b5f55350c3b6be6470b849c6566018419cd159d2cebc"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43de33d99a4ccc07234cecd81f385456b55b0ea9c39c9eebf42f024c313728a5"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139bed875828e1727018aa0982aa140e055cbafccb7fd89faf45cbb4f2a21514"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22c12671194b518aa8ce2f4422bd5064f25ab57f410ba0b78705d0a219f4a97a"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79888f2f7dc25709cd5d37b032a8833741e6a3692c8823be181d542b5999128e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:51628b4eb41fa25bd428f8f7b5b74fbb05f3ae65fbd265019a0dd1ded4fdf12a"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1db9eb7179285403d2fb56ba1ff6ec35a44921b5e2fa5ca19d69f3f9f0285ea5"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:08ab7efae08e55812340bfd1b3f09f63848fe291675e2105eab1aa5327d3a16e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e5fdc5264f884e7c0a1711a81dff112708a64b9c8561654ee578bfdccec6be09"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:90d6a2e6ab891043ee655ec99d5e77455a9bee9e1131bdfcfb745edde81200dd"}, + {file = "cytoolz-1.0.1-cp38-cp38-win32.whl", hash = "sha256:08946e083faa5147751b34fbf78ab931f149ef758af5c1092932b459e18dcf5c"}, + {file = "cytoolz-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a91b4e10a9c03796c0dc93e47ebe25bb41ecc6fafc3cf5197c603cf767a3d44d"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:980c323e626ba298b77ae62871b2de7c50b9d7219e2ddf706f52dd34b8be7349"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:45f6fa1b512bc2a0f2de5123db932df06c7f69d12874fe06d67772b2828e2c8b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93f42d9100c415155ad1f71b0de362541afd4ac95e3153467c4c79972521b6b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a76d20dec9c090cdf4746255bbf06a762e8cc29b5c9c1d138c380bbdb3122ade"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:239039585487c69aa50c5b78f6a422016297e9dea39755761202fb9f0530fe87"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28307640ca2ab57b9fbf0a834b9bf563958cd9e038378c3a559f45f13c3c541"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:454880477bb901cee3a60f6324ec48c95d45acc7fecbaa9d49a5af737ded0595"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:902115d1b1f360fd81e44def30ac309b8641661150fcbdde18ead446982ada6a"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e68e6b38473a3a79cee431baa22be31cac39f7df1bf23eaa737eaff42e213883"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32fba3f63fcb76095b0a22f4bdcc22bc62a2bd2d28d58bf02fd21754c155a3ec"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0724ba4cf41eb40b6cf75250820ab069e44bdf4183ff78857aaf4f0061551075"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c42420e0686f887040d5230420ed44f0e960ccbfa29a0d65a3acd9ca52459209"}, + {file = "cytoolz-1.0.1-cp39-cp39-win32.whl", hash = "sha256:4ba8b16358ea56b1fe8e637ec421e36580866f2e787910bac1cf0a6997424a34"}, + {file = "cytoolz-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:92d27f84bf44586853d9562bfa3610ecec000149d030f793b4cb614fd9da1813"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba0d1da50aab1909b165f615ba1125c8b01fcc30d606c42a61c42ea0269b5e2c"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25b6e8dec29aa5a390092d193abd673e027d2c0b50774ae816a31454286c45c7"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36cd6989ebb2f18fe9af8f13e3c61064b9f741a40d83dc5afeb0322338ad25f2"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47394f8ab7fca3201f40de61fdeea20a2baffb101485ae14901ea89c3f6c95d"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d00ac423542af944302e034e618fb055a0c4e87ba704cd6a79eacfa6ac83a3c9"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a5ca923d1fa632f7a4fb33c0766c6fba7f87141a055c305c3e47e256fb99c413"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058bf996bcae9aad3acaeeb937d42e0c77c081081e67e24e9578a6a353cb7fb2"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e2a1f41a3dad94a17aef4a5cc003323359b9f0a9d63d4cc867cb5690a2551d"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67daeeeadb012ec2b59d63cb29c4f2a2023b0c4957c3342d354b8bb44b209e9a"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:54d3d36bbf0d4344d1afa22c58725d1668e30ff9de3a8f56b03db1a6da0acb11"}, + {file = "cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6"}, ] [package.dependencies] @@ -1251,8 +1265,6 @@ files = [ anyio = ">=3.0,<5" backoff = ">=1.11.1,<3.0" graphql-core = ">=3.2,<3.3" -requests = {version = ">=2.26,<3", optional = true, markers = "extra == \"requests\""} -requests-toolbelt = {version = ">=1.0.0,<2", optional = true, markers = "extra == \"requests\""} yarl = ">=1.6,<2.0" [package.extras] @@ -1906,22 +1918,22 @@ files = [ [[package]] name = "protobuf" -version = "5.29.1" +version = "5.29.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.29.1-cp310-abi3-win32.whl", hash = "sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110"}, - {file = "protobuf-5.29.1-cp310-abi3-win_amd64.whl", hash = "sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34"}, - {file = "protobuf-5.29.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18"}, - {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155"}, - {file = "protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d"}, - {file = "protobuf-5.29.1-cp38-cp38-win32.whl", hash = "sha256:50879eb0eb1246e3a5eabbbe566b44b10348939b7cc1b267567e8c3d07213853"}, - {file = "protobuf-5.29.1-cp38-cp38-win_amd64.whl", hash = "sha256:027fbcc48cea65a6b17028510fdd054147057fa78f4772eb547b9274e5219331"}, - {file = "protobuf-5.29.1-cp39-cp39-win32.whl", hash = "sha256:5a41deccfa5e745cef5c65a560c76ec0ed8e70908a67cc8f4da5fce588b50d57"}, - {file = "protobuf-5.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c"}, - {file = "protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0"}, - {file = "protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb"}, + {file = "protobuf-5.29.2-cp310-abi3-win32.whl", hash = "sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851"}, + {file = "protobuf-5.29.2-cp310-abi3-win_amd64.whl", hash = "sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9"}, + {file = "protobuf-5.29.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb"}, + {file = "protobuf-5.29.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e"}, + {file = "protobuf-5.29.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e"}, + {file = "protobuf-5.29.2-cp38-cp38-win32.whl", hash = "sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19"}, + {file = "protobuf-5.29.2-cp38-cp38-win_amd64.whl", hash = "sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a"}, + {file = "protobuf-5.29.2-cp39-cp39-win32.whl", hash = "sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9"}, + {file = "protobuf-5.29.2-cp39-cp39-win_amd64.whl", hash = "sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355"}, + {file = "protobuf-5.29.2-py3-none-any.whl", hash = "sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181"}, + {file = "protobuf-5.29.2.tar.gz", hash = "sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e"}, ] [[package]] @@ -2332,20 +2344,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - [[package]] name = "rich" version = "13.9.4" @@ -2581,7 +2579,7 @@ test = ["py-ecc (==6.0.0)", "pytest (>=6.2.5)", "pytest-benchmark (>=3.2.3)"] [[package]] name = "sw-utils" -version = "v0.6.34" +version = "v0.7.3" description = "StakeWise Python utils" optional = false python-versions = "^3.10" @@ -2589,6 +2587,7 @@ files = [] develop = false [package.dependencies] +gql = "==3.5.0" ipfshttpclient = "^0.8.0a2" py-ecc = "^6.0.0" pyjwt = "==2.8.0" @@ -2600,8 +2599,8 @@ web3 = "==6.15.1" [package.source] type = "git" url = "https://github.com/stakewise/sw-utils.git" -reference = "v0.6.34" -resolved_reference = "1ed42c002049d771f73f0975834cfe4e8c9adf50" +reference = "v0.7.3" +resolved_reference = "443ef9413cbb812e3488d362dd6a5b637deda759" [[package]] name = "tenacity" @@ -2964,4 +2963,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "e570721f2b150c4bd7116458fc4053f40cf958a949cdcea165f6e5edb5b3ce22" +content-hash = "da651862c7f3fb3255a82348a9a4279dc9f836c34bc979e7cbc50bc1749ae30e" diff --git a/pyproject.toml b/pyproject.toml index e11ea0b..e4989f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,7 @@ package-mode = false [tool.poetry.dependencies] python = "^3.10" -gql = {extras = ["requests"], version = "==3.5.0"} -sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.6.34"} +sw-utils = {git = "https://github.com/stakewise/sw-utils.git", rev = "v0.7.3"} python-decouple = "==3.8" aiohttp = "==3.10.11" diff --git a/src/common/graph.py b/src/common/graph.py new file mode 100644 index 0000000..3ddb28c --- /dev/null +++ b/src/common/graph.py @@ -0,0 +1,48 @@ +import logging + +from eth_typing import ChecksumAddress +from gql import gql +from hexbytes import HexBytes +from sw_utils.graph.client import GraphClient +from web3 import Web3 +from web3.types import Wei + +from src.common.typings import HarvestParams + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +async def get_harvest_params( + graph_client: GraphClient, vault_address: ChecksumAddress +) -> HarvestParams | None: + query = gql( + """ + query VaultQuery($vault: String) { + vault( + id: $vault + ) { + proof + proofReward + proofUnlockedMevReward + rewardsRoot + } + } + """ + ) + params = { + 'vault': vault_address.lower(), + } + + response = await graph_client.run_query(query, params) + vault_data = response['vault'] # pylint: disable=unsubscriptable-object + + if not all(vault_data): + return None + + return HarvestParams( + rewards_root=HexBytes(Web3.to_bytes(hexstr=vault_data['rewardsRoot'])), + reward=Wei(int(vault_data['proofReward'])), + unlocked_mev_reward=Wei(int(vault_data['proofUnlockedMevReward'])), + proof=[HexBytes(Web3.to_bytes(hexstr=p)) for p in vault_data['proof']], + ) diff --git a/src/exit/clients.py b/src/exit/clients.py index 082e72f..8c9809a 100644 --- a/src/exit/clients.py +++ b/src/exit/clients.py @@ -1,25 +1,20 @@ import logging -import gql -from gql.transport.requests import RequestsHTTPTransport +from sw_utils.graph.client import GraphClient from src.common.clients import get_execution_client, hot_wallet_account -from src.common.settings import EXECUTION_ENDPOINT +from src.common.settings import EXECUTION_ENDPOINT, GRAPH_PAGE_SIZE -from .settings import GRAPH_API_TIMEOUT, GRAPH_API_URL +from .settings import GRAPH_API_RETRY_TIMEOUT, GRAPH_API_TIMEOUT, GRAPH_API_URL logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) execution_client = get_execution_client(EXECUTION_ENDPOINT, account=hot_wallet_account) - -def get_graph_client() -> gql.Client: - transport = RequestsHTTPTransport( - url=GRAPH_API_URL, - timeout=GRAPH_API_TIMEOUT, - ) - return gql.Client(transport=transport) - - -graph_client = get_graph_client() +graph_client = GraphClient( + endpoint=GRAPH_API_URL, + request_timeout=GRAPH_API_TIMEOUT, + retry_timeout=GRAPH_API_RETRY_TIMEOUT, + page_size=GRAPH_PAGE_SIZE, +) diff --git a/src/exit/execution.py b/src/exit/execution.py index 2e843c9..f3ba567 100644 --- a/src/exit/execution.py +++ b/src/exit/execution.py @@ -67,13 +67,14 @@ def claim_exited_assets( try: tx = multicall_contract.functions.aggregate(calls).transact() except Exception as e: - logger.error( - 'Failed to claim exited assets; vault=%s, position_ticket=%s %s: ', + logger.info( + 'Failed to claim exited assets for leverage positions: vault=%s, user=%s %s...', vault, - exit_request.position_ticket, + user, e, ) logger.exception(e) + return None tx_hash = Web3.to_hex(tx) @@ -82,7 +83,11 @@ def claim_exited_assets( tx, timeout=EXECUTION_TRANSACTION_TIMEOUT ) if not tx_receipt['status']: - logger.error('Exited assets claim transaction failed') + logger.info( + 'Failed to claim exited assets for leverage positions: vault=%s, user=%s...', + vault, + user, + ) return None return tx_hash diff --git a/src/exit/graph.py b/src/exit/graph.py index 7334c9a..7699d56 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -1,35 +1,15 @@ -from typing import Any - from gql import gql -from graphql import DocumentNode from web3 import Web3 from web3.types import BlockNumber, ChecksumAddress -from src.common.settings import GRAPH_PAGE_SIZE +from src.common.graph import get_harvest_params +from src.common.typings import HarvestParams from .clients import graph_client from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest -def paginate_query(query: DocumentNode, params: dict) -> list: - res: list[Any] = [] - page_res = None - first = GRAPH_PAGE_SIZE - skip = 0 - - while page_res is None or len(page_res): - params['first'] = first - params['skip'] = skip - - response = graph_client.execute(query, params) - page_res = list(response.values())[0] - - skip += first - res.extend(page_res) - return res - - -def graph_get_leverage_positions( +async def graph_get_leverage_positions( borrow_ltv: float, block_number: BlockNumber ) -> list[LeveragePosition]: query = gql( @@ -49,7 +29,6 @@ def graph_get_leverage_positions( id } exitRequest { - id positionTicket timestamp exitQueueIndex @@ -62,7 +41,7 @@ def graph_get_leverage_positions( """ ) params = {'block': block_number, 'borrowLTV': str(borrow_ltv)} - response = paginate_query(query, params) + response = await graph_client.fetch_pages(query, params=params) result = [] for data in response: position = LeveragePosition( @@ -77,7 +56,7 @@ def graph_get_leverage_positions( return result -def graph_get_allocators( +async def graph_get_allocators( ltv: float, addresses: list[ChecksumAddress], block_number: BlockNumber ) -> list[ChecksumAddress]: query = gql( @@ -107,7 +86,7 @@ def graph_get_allocators( 'addresses': [address.lower() for address in addresses], 'block': block_number, } - response = paginate_query(query, params) + response = await graph_client.fetch_pages(query, params=params) result = [] for data in response: result.append( @@ -116,7 +95,9 @@ def graph_get_allocators( return result -def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsTokenExitRequest]: +async def graph_ostoken_exit_requests( + ltv: int, block_number: BlockNumber +) -> list[OsTokenExitRequest]: query = gql( """ query ExitRequestsQuery($ltv: String, $block: Int, $first: Int, $skip: Int) { @@ -139,9 +120,9 @@ def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsT """ ) params = {'ltv': str(ltv), 'block': block_number} - response = paginate_query(query, params) + response = await graph_client.fetch_pages(query, params=params) - exit_requests = graph_get_exit_requests( + exit_requests = await graph_get_exit_requests( ids=[item['id'] for item in response], block_number=block_number ) id_to_exit_request = {exit.id: exit for exit in exit_requests} @@ -161,7 +142,7 @@ def graph_ostoken_exit_requests(ltv: int, block_number: BlockNumber) -> list[OsT return result -def graph_get_exit_requests(ids: list[str], block_number: BlockNumber) -> list[ExitRequest]: +async def graph_get_exit_requests(ids: list[str], block_number: BlockNumber) -> list[ExitRequest]: query = gql( """ query exitRequestQuery($ids: [String], $block: Int, $first: Int, $skip: Int) { @@ -184,8 +165,12 @@ def graph_get_exit_requests(ids: list[str], block_number: BlockNumber) -> list[E """ ) params = {'block': block_number, 'ids': ids} - response = paginate_query(query, params) + response = await graph_client.fetch_pages(query, params=params) result = [] for data in response: result.append(ExitRequest.from_graph(data)) return result + + +async def graph_get_harvest_params(vault_address: ChecksumAddress) -> HarvestParams | None: + return await get_harvest_params(graph_client, vault_address) diff --git a/src/exit/settings.py b/src/exit/settings.py index b558027..4d3be6f 100644 --- a/src/exit/settings.py +++ b/src/exit/settings.py @@ -3,3 +3,4 @@ # graph GRAPH_API_URL: str = config('GRAPH_API_URL') GRAPH_API_TIMEOUT: int = config('GRAPH_API_TIMEOUT', default='10', cast=int) +GRAPH_API_RETRY_TIMEOUT: int = config('GRAPH_API_RETRY_TIMEOUT', default='60', cast=int) diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 5fdf17f..35106d0 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -30,7 +30,7 @@ WAD = 10**18 -def force_exits() -> None: +async def force_exits() -> None: """ Monitor leverage positions and trigger exits/claims for those that approach the liquidation threshold. @@ -38,16 +38,16 @@ def force_exits() -> None: block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) block_number = block['number'] - handle_leverage_positions(block_number) - handel_exit_position(block_number) + await handle_leverage_positions(block_number) + await handle_ostoken_exit_requests(block_number) -def handle_leverage_positions(block_number: BlockNumber) -> None: +async def handle_leverage_positions(block_number: BlockNumber) -> None: """Process graph leverage positions.""" strategy_id = leverage_strategy_contract.strategy_id() borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD - leverage_positions = graph_get_leverage_positions( + leverage_positions = await graph_get_leverage_positions( borrow_ltv=borrow_ltv, block_number=block_number ) logger.info('Checking %d leverage positions...', len(leverage_positions)) @@ -57,7 +57,7 @@ def handle_leverage_positions(block_number: BlockNumber) -> None: for position in leverage_positions: harvest_params = vault_to_harvest_params.get(position.vault) if not harvest_params: - harvest_params = graph_get_harvest_params(position.vault) + harvest_params = await graph_get_harvest_params(position.vault) vault_to_harvest_params[position.vault] = harvest_params handle_leverage_position( @@ -67,8 +67,13 @@ def handle_leverage_positions(block_number: BlockNumber) -> None: ) # check by position proxy ltv + + # refetch leverage positions # todo: should be next block + leverage_positions = await graph_get_leverage_positions( + borrow_ltv=borrow_ltv, block_number=block_number + ) proxy_to_position = {position.proxy: position for position in leverage_positions} - allocators = graph_get_allocators( + allocators = await graph_get_allocators( ltv=vault_ltv, addresses=list(proxy_to_position.keys()), block_number=block_number ) leverage_positions_by_ltv = [] @@ -78,7 +83,7 @@ def handle_leverage_positions(block_number: BlockNumber) -> None: for position in leverage_positions_by_ltv: harvest_params = vault_to_harvest_params.get(position.vault) if not harvest_params: - harvest_params = graph_get_harvest_params(position.vault) + harvest_params = await graph_get_harvest_params(position.vault) vault_to_harvest_params[position.vault] = harvest_params handle_leverage_position( @@ -88,12 +93,12 @@ def handle_leverage_positions(block_number: BlockNumber) -> None: ) -def handel_exit_position(block_number: BlockNumber) -> None: +async def handle_ostoken_exit_requests(block_number: BlockNumber) -> None: """Process osTokenExitRequests from graph and claim exited assets.""" # force claim for exit positions max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() max_ltv_percent = max_ltv_percent // WAD - exit_requests = graph_ostoken_exit_requests(max_ltv_percent, block_number=block_number) + exit_requests = await graph_ostoken_exit_requests(max_ltv_percent, block_number=block_number) exit_requests = [ exit_request for exit_request in exit_requests if exit_request.exit_request.can_be_claimed ] @@ -105,7 +110,7 @@ def handel_exit_position(block_number: BlockNumber) -> None: vault = os_token_exit_request.vault harvest_params = vault_to_harvest_params.get(vault) if not harvest_params: - harvest_params = graph_get_harvest_params(vault) + harvest_params = await graph_get_harvest_params(vault) vault_to_harvest_params[vault] = harvest_params logger.info( @@ -141,6 +146,11 @@ def handle_leverage_position( harvest_params=harvest_params, block_number=block_number, ): + logger.info( + 'Skip leverage positions because it can\'t be forcefully closed: vault=%s, user=%s...', + position.vault, + position.user, + ) return # claim active exit request @@ -170,6 +180,21 @@ def handle_leverage_position( position.vault, position.user, ) + + # recheck because position state has changed after claiming assets + if not can_force_enter_exit_queue( + vault=position.vault, + user=position.user, + harvest_params=harvest_params, + block_number=block_number, + ): + logger.info( + 'Skip leverage positions because it can\'t be forcefully closed: vault=%s, user=%s...', + position.vault, + position.user, + ) + return + tx_hash = force_enter_exit_queue( vault=position.vault, user=position.user, diff --git a/src/force_exit.py b/src/force_exit.py index 3ef5259..621ecc0 100644 --- a/src/force_exit.py +++ b/src/force_exit.py @@ -1,3 +1,4 @@ +import asyncio import logging import sys @@ -6,9 +7,14 @@ logger = logging.getLogger(__name__) + +async def main() -> None: + await force_exits() + + try: setup_gql_log_level() - force_exits() + asyncio.run(main()) except Exception as e: logger.exception(e) sys.exit(1) diff --git a/src/ltv/clients.py b/src/ltv/clients.py index 082e72f..b80a475 100644 --- a/src/ltv/clients.py +++ b/src/ltv/clients.py @@ -1,12 +1,11 @@ import logging -import gql -from gql.transport.requests import RequestsHTTPTransport +from sw_utils.graph.client import GraphClient from src.common.clients import get_execution_client, hot_wallet_account -from src.common.settings import EXECUTION_ENDPOINT +from src.common.settings import EXECUTION_ENDPOINT, GRAPH_PAGE_SIZE -from .settings import GRAPH_API_TIMEOUT, GRAPH_API_URL +from .settings import GRAPH_API_RETRY_TIMEOUT, GRAPH_API_TIMEOUT, GRAPH_API_URL logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -14,12 +13,9 @@ execution_client = get_execution_client(EXECUTION_ENDPOINT, account=hot_wallet_account) -def get_graph_client() -> gql.Client: - transport = RequestsHTTPTransport( - url=GRAPH_API_URL, - timeout=GRAPH_API_TIMEOUT, - ) - return gql.Client(transport=transport) - - -graph_client = get_graph_client() +graph_client = GraphClient( + endpoint=GRAPH_API_URL, + request_timeout=GRAPH_API_TIMEOUT, + retry_timeout=GRAPH_API_RETRY_TIMEOUT, + page_size=GRAPH_PAGE_SIZE, +) diff --git a/src/ltv/graph.py b/src/ltv/graph.py index a9a2265..29bce6a 100644 --- a/src/ltv/graph.py +++ b/src/ltv/graph.py @@ -2,10 +2,9 @@ from eth_typing import ChecksumAddress from gql import gql -from hexbytes import HexBytes from web3 import Web3 -from web3.types import Wei +from src.common.graph import get_harvest_params from src.common.typings import HarvestParams from .clients import graph_client @@ -14,7 +13,7 @@ logger = logging.getLogger(__name__) -def graph_get_ostoken_vaults() -> list[ChecksumAddress]: +async def graph_get_ostoken_vaults() -> list[ChecksumAddress]: query = gql( """ query OsTokenVaultsIds { @@ -25,12 +24,12 @@ def graph_get_ostoken_vaults() -> list[ChecksumAddress]: """ ) - response = graph_client.execute(query) + response = await graph_client.run_query(query) vaults = response['networks'][0]['osTokenVaultIds'] # pylint: disable=unsubscriptable-object return [Web3.to_checksum_address(vault) for vault in vaults] -def graph_get_vault_max_ltv_allocator(vault_address: str) -> ChecksumAddress | None: +async def graph_get_vault_max_ltv_allocator(vault_address: str) -> ChecksumAddress | None: query = gql( """ query AllocatorsQuery($vault: String) { @@ -49,7 +48,7 @@ def graph_get_vault_max_ltv_allocator(vault_address: str) -> ChecksumAddress | N 'vault': vault_address.lower(), } - response = graph_client.execute(query, params) + response = await graph_client.run_query(query, params) allocators = response['allocators'] # pylint: disable=unsubscriptable-object if not allocators: @@ -58,34 +57,5 @@ def graph_get_vault_max_ltv_allocator(vault_address: str) -> ChecksumAddress | N return Web3.to_checksum_address(allocators[0]['address']) -def graph_get_harvest_params(vault_address: ChecksumAddress) -> HarvestParams | None: - query = gql( - """ - query VaultQuery($vault: String) { - vault( - id: $vault - ) { - proof - proofReward - proofUnlockedMevReward - rewardsRoot - } - } - """ - ) - params = { - 'vault': vault_address.lower(), - } - - response = graph_client.execute(query, params) - vault_data = response['vault'] # pylint: disable=unsubscriptable-object - - if not all(vault_data): - return None - - return HarvestParams( - rewards_root=HexBytes(Web3.to_bytes(hexstr=vault_data['rewardsRoot'])), - reward=Wei(int(vault_data['proofReward'])), - unlocked_mev_reward=Wei(int(vault_data['proofUnlockedMevReward'])), - proof=[HexBytes(Web3.to_bytes(hexstr=p)) for p in vault_data['proof']], - ) +async def graph_get_harvest_params(vault_address: ChecksumAddress) -> HarvestParams | None: + return await get_harvest_params(graph_client, vault_address) diff --git a/src/ltv/settings.py b/src/ltv/settings.py index b558027..4d3be6f 100644 --- a/src/ltv/settings.py +++ b/src/ltv/settings.py @@ -3,3 +3,4 @@ # graph GRAPH_API_URL: str = config('GRAPH_API_URL') GRAPH_API_TIMEOUT: int = config('GRAPH_API_TIMEOUT', default='10', cast=int) +GRAPH_API_RETRY_TIMEOUT: int = config('GRAPH_API_RETRY_TIMEOUT', default='60', cast=int) diff --git a/src/ltv/tasks.py b/src/ltv/tasks.py index 2029939..bdd93eb 100644 --- a/src/ltv/tasks.py +++ b/src/ltv/tasks.py @@ -19,7 +19,7 @@ WAD = 10**18 -def update_vault_max_ltv_user() -> None: +async def update_vault_max_ltv_user() -> None: """ Finds user having maximum LTV in given vault and submits this user in the LTV Tracker contract. """ @@ -27,21 +27,21 @@ def update_vault_max_ltv_user() -> None: block = execution_client.eth.get_block('finalized') logger.debug('Current block: %d', block['number']) - ostoken_vaults = graph_get_ostoken_vaults() + ostoken_vaults = await graph_get_ostoken_vaults() for vault in ostoken_vaults: - handle_vault(vault) + await handle_vault(vault) logger.info('Completed') -def handle_vault(vault: ChecksumAddress) -> None: - max_ltv_user = graph_get_vault_max_ltv_allocator(vault) +async def handle_vault(vault: ChecksumAddress) -> None: + max_ltv_user = await graph_get_vault_max_ltv_allocator(vault) if max_ltv_user is None: logger.warning('No allocators in vault %s', vault) return logger.info('max LTV user for vault %s is %s', vault, max_ltv_user) - harvest_params = graph_get_harvest_params(vault) + harvest_params = await graph_get_harvest_params(vault) logger.debug('Harvest params for vault %s: %s', vault, harvest_params) # Get current LTV diff --git a/src/update_ltv.py b/src/update_ltv.py index d190e31..59928b3 100644 --- a/src/update_ltv.py +++ b/src/update_ltv.py @@ -1,3 +1,4 @@ +import asyncio import logging import sys @@ -6,9 +7,14 @@ logger = logging.getLogger(__name__) + +async def main() -> None: + await update_vault_max_ltv_user() + + try: setup_gql_log_level() - update_vault_max_ltv_user() + asyncio.run(main()) except Exception as e: logger.error(e) sys.exit(1) From ce76f8dd1b551d98e433c9cd03796909b8193a8a Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 19 Dec 2024 22:16:20 +0300 Subject: [PATCH 15/19] Fix graph logger --- src/common/logs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logs.py b/src/common/logs.py index 68b5fc9..643d770 100644 --- a/src/common/logs.py +++ b/src/common/logs.py @@ -1,6 +1,6 @@ import logging -from gql.transport.requests import log as requests_logger +from gql.transport.aiohttp import log as requests_logger def setup_gql_log_level() -> None: From 8e9559659260ab1bc9405ab9bf0ab9f42a0fd403 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Mon, 23 Dec 2024 14:46:03 +0300 Subject: [PATCH 16/19] Process positions in single iteration --- src/exit/graph.py | 12 ++++++------ src/exit/tasks.py | 43 +++++++++++++++++-------------------------- src/exit/typings.py | 5 +++++ 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/exit/graph.py b/src/exit/graph.py index 7699d56..82debe7 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -9,15 +9,12 @@ from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest -async def graph_get_leverage_positions( - borrow_ltv: float, block_number: BlockNumber -) -> list[LeveragePosition]: +async def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosition]: query = gql( """ - query PositionsQuery($borrowLTV: String, $block: Int, $first: Int, $skip: Int) { + query PositionsQuery($block: Int, $first: Int, $skip: Int) { leverageStrategyPositions( block: { number: $block }, - where: { borrowLtv_gt: $borrowLTV }, orderBy: borrowLtv, orderDirection: desc, first: $first, @@ -25,10 +22,12 @@ async def graph_get_leverage_positions( ) { user proxy + borrowLtv vault { id } exitRequest { + id positionTicket timestamp exitQueueIndex @@ -40,7 +39,7 @@ async def graph_get_leverage_positions( } """ ) - params = {'block': block_number, 'borrowLTV': str(borrow_ltv)} + params = {'block': block_number} response = await graph_client.fetch_pages(query, params=params) result = [] for data in response: @@ -48,6 +47,7 @@ async def graph_get_leverage_positions( vault=Web3.to_checksum_address(data['vault']['id']), user=Web3.to_checksum_address(data['user']), proxy=Web3.to_checksum_address(data['proxy']), + borrow_ltv=float(data['borrowLtv']), ) if data['exitRequest']: position.exit_request = ExitRequest.from_graph(data['exitRequest']) diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 35106d0..520bc9b 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -47,32 +47,11 @@ async def handle_leverage_positions(block_number: BlockNumber) -> None: strategy_id = leverage_strategy_contract.strategy_id() borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD - leverage_positions = await graph_get_leverage_positions( - borrow_ltv=borrow_ltv, block_number=block_number - ) - logger.info('Checking %d leverage positions...', len(leverage_positions)) - - vault_to_harvest_params: dict[ChecksumAddress, HarvestParams | None] = {} - # check by position borrow ltv - for position in leverage_positions: - harvest_params = vault_to_harvest_params.get(position.vault) - if not harvest_params: - harvest_params = await graph_get_harvest_params(position.vault) - vault_to_harvest_params[position.vault] = harvest_params - - handle_leverage_position( - position=position, - harvest_params=harvest_params, - block_number=block_number, - ) - - # check by position proxy ltv - - # refetch leverage positions # todo: should be next block - leverage_positions = await graph_get_leverage_positions( - borrow_ltv=borrow_ltv, block_number=block_number - ) - proxy_to_position = {position.proxy: position for position in leverage_positions} + all_leverage_positions = await graph_get_leverage_positions(block_number=block_number) + # Get positions by borrow ltv + borrow_ltv_positions = [pos for pos in all_leverage_positions if pos.borrow_ltv > borrow_ltv] + # Get vault position by vault ltv + proxy_to_position = {position.proxy: position for position in all_leverage_positions} allocators = await graph_get_allocators( ltv=vault_ltv, addresses=list(proxy_to_position.keys()), block_number=block_number ) @@ -80,7 +59,19 @@ async def handle_leverage_positions(block_number: BlockNumber) -> None: for allocator in allocators: leverage_positions_by_ltv.append(proxy_to_position[allocator]) + # join positions + leverage_positions = [] + leverage_positions.extend(borrow_ltv_positions) + borrow_ltv_positions_ids = [pos.id for pos in borrow_ltv_positions] for position in leverage_positions_by_ltv: + if position.id not in borrow_ltv_positions_ids: + leverage_positions.append(position) + + logger.info('Checking %d leverage positions...', len(leverage_positions)) + + vault_to_harvest_params: dict[ChecksumAddress, HarvestParams | None] = {} + # check by position borrow ltv + for position in leverage_positions: harvest_params = vault_to_harvest_params.get(position.vault) if not harvest_params: harvest_params = await graph_get_harvest_params(position.vault) diff --git a/src/exit/typings.py b/src/exit/typings.py index 6d478f9..95a73da 100644 --- a/src/exit/typings.py +++ b/src/exit/typings.py @@ -38,8 +38,13 @@ class LeveragePosition: user: ChecksumAddress vault: ChecksumAddress proxy: ChecksumAddress + borrow_ltv: float exit_request: ExitRequest | None = None + @property + def id(self) -> str: + return f'{self.vault}-{self.user}' + @dataclass class OsTokenExitRequest: From 2d5fa63a5402af7c6a26850a4bee3fbf8340d8e5 Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 26 Dec 2024 12:16:22 +0300 Subject: [PATCH 17/19] Update README --- .env.example | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) diff --git a/.env.example b/.env.example index 8c76397..8b6f88b 100644 --- a/.env.example +++ b/.env.example @@ -17,3 +17,7 @@ HOT_WALLET_PRIVATE_KEY=0x2...... # Settings for update_ltv.py # EXECUTION_ENDPOINT=https://execution # GRAPH_API_URL=https://graph + +# Settings for force_exit.py +# EXECUTION_ENDPOINT=https://execution +# GRAPH_API_URL=https://graph diff --git a/README.md b/README.md index 98e7229..7ac2e6d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ List of scripts: * `src/update_price.py` - calls the function on the mainnet that updates the osETH-to-ETH price on the L2 network, i.e., the target network. * `src/update_ltv.py` - updates user having maximum LTV in given vault. Users are stored in `VaultUserLtvTracker` contract. +* `src/force_exit.py` - Monitor leverage positions and trigger exits/claims for those that approach the liquidation threshold. ## Setup From 0d395ccf0db8f40dd332e2188fbf2754e9a452fa Mon Sep 17 00:00:00 2001 From: cyc60 Date: Thu, 26 Dec 2024 12:21:11 +0300 Subject: [PATCH 18/19] Add sentry to force exits --- src/force_exit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/force_exit.py b/src/force_exit.py index 621ecc0..9242e7e 100644 --- a/src/force_exit.py +++ b/src/force_exit.py @@ -3,6 +3,7 @@ import sys from src.common.logs import setup_gql_log_level +from src.common.sentry import setup_sentry from src.exit.tasks import force_exits logger = logging.getLogger(__name__) @@ -13,6 +14,7 @@ async def main() -> None: try: + setup_sentry() setup_gql_log_level() asyncio.run(main()) except Exception as e: From e401c63a5d58a1c9ff9e6102e938a1c687c2bc77 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Thu, 26 Dec 2024 14:12:27 +0200 Subject: [PATCH 19/19] Fix force exits (#7) * Fix force exits * Fix linting --- src/exit/execution.py | 14 +++++++++----- src/exit/graph.py | 31 ++++++++++++++++++++++++++----- src/exit/tasks.py | 31 +++++++++++++++++-------------- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/exit/execution.py b/src/exit/execution.py index f3ba567..61b7860 100644 --- a/src/exit/execution.py +++ b/src/exit/execution.py @@ -67,8 +67,8 @@ def claim_exited_assets( try: tx = multicall_contract.functions.aggregate(calls).transact() except Exception as e: - logger.info( - 'Failed to claim exited assets for leverage positions: vault=%s, user=%s %s...', + logger.error( + 'Failed to claim exited assets for leverage position: vault=%s, user=%s %s...', vault, user, e, @@ -83,8 +83,8 @@ def claim_exited_assets( tx, timeout=EXECUTION_TRANSACTION_TIMEOUT ) if not tx_receipt['status']: - logger.info( - 'Failed to claim exited assets for leverage positions: vault=%s, user=%s...', + logger.error( + 'Failed to confirm exited assets claim for leverage position: vault=%s, user=%s...', vault, user, ) @@ -125,7 +125,11 @@ def force_enter_exit_queue( tx, timeout=EXECUTION_TRANSACTION_TIMEOUT ) if not tx_receipt['status']: - logger.error('Force enter exit queue transaction failed') + logger.error( + 'Failed to confirm force enter exit queue: vault=%s, user=%s...', + vault, + user, + ) return None return tx_hash diff --git a/src/exit/graph.py b/src/exit/graph.py index 82debe7..566b783 100644 --- a/src/exit/graph.py +++ b/src/exit/graph.py @@ -1,13 +1,24 @@ from gql import gql +from sw_utils.networks import HOLESKY from web3 import Web3 from web3.types import BlockNumber, ChecksumAddress from src.common.graph import get_harvest_params +from src.common.settings import NETWORK from src.common.typings import HarvestParams from .clients import graph_client from .typings import ExitRequest, LeveragePosition, OsTokenExitRequest +DISABLED_LIQ_THRESHOLD = 2**64 - 1 +HOLESKY_UNCLAIMABLE_EXIT_REQUEST_IDS = [ + '0x8a94e1d22d83990205843cda08376d16f150c9bb-210258902756807306422', + '0x8a94e1d22d83990205843cda08376d16f150c9bb-450147843736954431325', + '0x8a94e1d22d83990205843cda08376d16f150c9bb-458856763747647876703', + '0x8a94e1d22d83990205843cda08376d16f150c9bb-464067729736660634975', + '0x8a94e1d22d83990205843cda08376d16f150c9bb-465799992852070364982', +] + async def graph_get_leverage_positions(block_number: BlockNumber) -> list[LeveragePosition]: query = gql( @@ -77,6 +88,11 @@ async def graph_get_allocators( skip: $skip ) { address + vault { + osTokenConfig { + liqThresholdPercent + } + } } } """ @@ -89,14 +105,16 @@ async def graph_get_allocators( response = await graph_client.fetch_pages(query, params=params) result = [] for data in response: - result.append( - Web3.to_checksum_address(data['address']), - ) + vault_liq_threshold = int(data['vault']['osTokenConfig']['liqThresholdPercent']) + if vault_liq_threshold != DISABLED_LIQ_THRESHOLD: + result.append( + Web3.to_checksum_address(data['address']), + ) return result async def graph_ostoken_exit_requests( - ltv: int, block_number: BlockNumber + ltv: float, block_number: BlockNumber ) -> list[OsTokenExitRequest]: query = gql( """ @@ -125,10 +143,13 @@ async def graph_ostoken_exit_requests( exit_requests = await graph_get_exit_requests( ids=[item['id'] for item in response], block_number=block_number ) - id_to_exit_request = {exit.id: exit for exit in exit_requests} + id_to_exit_request = {exit_req.id: exit_req for exit_req in exit_requests} result = [] for data in response: + if NETWORK == HOLESKY and data['id'] in HOLESKY_UNCLAIMABLE_EXIT_REQUEST_IDS: + continue + result.append( OsTokenExitRequest( id=data['id'], diff --git a/src/exit/tasks.py b/src/exit/tasks.py index 520bc9b..e4b6d81 100644 --- a/src/exit/tasks.py +++ b/src/exit/tasks.py @@ -48,25 +48,29 @@ async def handle_leverage_positions(block_number: BlockNumber) -> None: borrow_ltv = strategy_registry_contract.get_borrow_ltv_percent(strategy_id) / WAD vault_ltv = strategy_registry_contract.get_vault_ltv_percent(strategy_id) / WAD all_leverage_positions = await graph_get_leverage_positions(block_number=block_number) - # Get positions by borrow ltv - borrow_ltv_positions = [pos for pos in all_leverage_positions if pos.borrow_ltv > borrow_ltv] - # Get vault position by vault ltv + # Get aave positions by borrow ltv + aave_positions = [pos for pos in all_leverage_positions if pos.borrow_ltv > borrow_ltv] + # Get vault positions by vault ltv proxy_to_position = {position.proxy: position for position in all_leverage_positions} allocators = await graph_get_allocators( ltv=vault_ltv, addresses=list(proxy_to_position.keys()), block_number=block_number ) - leverage_positions_by_ltv = [] + vault_positions = [] for allocator in allocators: - leverage_positions_by_ltv.append(proxy_to_position[allocator]) + vault_positions.append(proxy_to_position[allocator]) # join positions leverage_positions = [] - leverage_positions.extend(borrow_ltv_positions) - borrow_ltv_positions_ids = [pos.id for pos in borrow_ltv_positions] - for position in leverage_positions_by_ltv: + leverage_positions.extend(aave_positions) + borrow_ltv_positions_ids = set(pos.id for pos in aave_positions) + for position in vault_positions: if position.id not in borrow_ltv_positions_ids: leverage_positions.append(position) + if not leverage_positions: + logger.info('No risky leverage positions found...') + return + logger.info('Checking %d leverage positions...', len(leverage_positions)) vault_to_harvest_params: dict[ChecksumAddress, HarvestParams | None] = {} @@ -87,8 +91,7 @@ async def handle_leverage_positions(block_number: BlockNumber) -> None: async def handle_ostoken_exit_requests(block_number: BlockNumber) -> None: """Process osTokenExitRequests from graph and claim exited assets.""" # force claim for exit positions - max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() - max_ltv_percent = max_ltv_percent // WAD + max_ltv_percent = ostoken_vault_escrow_contract.liq_threshold_percent() / WAD exit_requests = await graph_ostoken_exit_requests(max_ltv_percent, block_number=block_number) exit_requests = [ exit_request for exit_request in exit_requests if exit_request.exit_request.can_be_claimed @@ -128,8 +131,8 @@ def handle_leverage_position( position: LeveragePosition, harvest_params: HarvestParams | None, block_number: BlockNumber ) -> None: """ - Submit force exit for leverage postion. - Also check for position active exit request and claim assets is possible. + Submit force exit for leverage position. + Also check for position active exit request and claim assets if possible. """ if not can_force_enter_exit_queue( vault=position.vault, @@ -138,7 +141,7 @@ def handle_leverage_position( block_number=block_number, ): logger.info( - 'Skip leverage positions because it can\'t be forcefully closed: vault=%s, user=%s...', + 'Skip leverage positions because it cannot be forcefully closed: vault=%s, user=%s...', position.vault, position.user, ) @@ -180,7 +183,7 @@ def handle_leverage_position( block_number=block_number, ): logger.info( - 'Skip leverage positions because it can\'t be forcefully closed: vault=%s, user=%s...', + 'Skip leverage positions because it cannot be forcefully closed: vault=%s, user=%s...', position.vault, position.user, )