From 9ebe4c3d8c5dc950fd94df46bf7cd349bc319243 Mon Sep 17 00:00:00 2001 From: cjeva10 Date: Wed, 4 Dec 2024 14:30:14 -0500 Subject: [PATCH] add integration for upsUSDe --- abi/ERC4626_abi.json | 662 ++++++++++++++++++++ integrations/integration_ids.py | 2 + integrations/upshift_upsusde_integration.py | 138 ++++ 3 files changed, 802 insertions(+) create mode 100644 abi/ERC4626_abi.json create mode 100644 integrations/upshift_upsusde_integration.py diff --git a/abi/ERC4626_abi.json b/abi/ERC4626_abi.json new file mode 100644 index 0000000..2ad6f97 --- /dev/null +++ b/abi/ERC4626_abi.json @@ -0,0 +1,662 @@ +[ + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "convertToAssets", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "convertToShares", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decreaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "subtractedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "increaseAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "addedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "maxDeposit", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxMint", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxRedeem", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "maxWithdraw", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewDeposit", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewMint", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewRedeem", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "previewWithdraw", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalAssets", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receiver", + "type": "address", + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Deposit", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assets", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "shares", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Withdraw", + "inputs": [ + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "receiver", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "assets", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "shares", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + } +] \ No newline at end of file diff --git a/integrations/integration_ids.py b/integrations/integration_ids.py index aa89852..86e9618 100644 --- a/integrations/integration_ids.py +++ b/integrations/integration_ids.py @@ -393,6 +393,8 @@ class IntegrationID(Enum): "Beefy Cached Balance Example", Token.USDE, ) + # Upshift sUSDe + UPSHIFT_UPSUSDE = ("upshift_upsusde", "Upshift upsUSDe", Token.SUSDE) def __init__(self, column_name: str, description: str, token: Token = Token.USDE): self.column_name = column_name diff --git a/integrations/upshift_upsusde_integration.py b/integrations/upshift_upsusde_integration.py new file mode 100644 index 0000000..8824507 --- /dev/null +++ b/integrations/upshift_upsusde_integration.py @@ -0,0 +1,138 @@ +import json +import logging +from copy import deepcopy +from typing import Callable, Dict, List, Optional, Set + +from eth_typing import ChecksumAddress +from web3 import Web3 +from utils.web3_utils import w3 + +from constants.chains import Chain +from constants.example_integrations import PAGINATION_SIZE +from constants.summary_columns import SummaryColumn +from integrations.cached_balances_integration import CachedBalancesIntegration +from integrations.integration_ids import IntegrationID +from utils.web3_utils import fetch_events_logs_with_retry + +UPSUSDE_ADDRESS = Web3.to_checksum_address("0xd684AF965b1c17D628ee0d77cae94259c41260F4") +with open("abi/ERC4626_abi.json") as f: + ERC4626_ABI = json.load(f) + +UPSUSDE_CONTRACT = w3.eth.contract( + address=UPSUSDE_ADDRESS, + abi=ERC4626_ABI, +) +ZERO_ADDRESS = Web3.to_checksum_address("0x0000000000000000000000000000000000000000") + + +class UpshiftupsUSDeIntegration(CachedBalancesIntegration): + def __init__( + self, + integration_id: IntegrationID, + start_block: int, + chain: Chain = Chain.ETHEREUM, + summary_cols: Optional[List[SummaryColumn]] = None, + reward_multiplier: int = 1, + balance_multiplier: int = 1, + excluded_addresses: Optional[Set[ChecksumAddress]] = None, + end_block: Optional[int] = None, + ethereal_multiplier: int = 0, + ethereal_multiplier_func: Optional[Callable[[int, str], int]] = None, + ): + super().__init__( + integration_id, + start_block, + chain, + summary_cols, + reward_multiplier, + balance_multiplier, + excluded_addresses, + end_block, + ethereal_multiplier, + ethereal_multiplier_func, + ) + + def get_block_balances( + self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int] + ) -> Dict[int, Dict[ChecksumAddress, float]]: + logging.info("Getting block data for upSUSDe") + new_block_data: Dict[int, Dict[ChecksumAddress, float]] = {} + if not blocks: + logging.error("No blocks provided to get_block_balances") + return new_block_data + sorted_blocks = sorted(blocks) + cache_copy: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data) + for block in sorted_blocks: + total_supply = UPSUSDE_CONTRACT.functions.totalSupply().call( + block_identifier=block + ) + total_assets = UPSUSDE_CONTRACT.functions.totalAssets().call( + block_identifier=block + ) + + # convert balance of upsUSDe to implied sUSDe for this block + assets_per_share = total_assets / total_supply if total_supply != 0 else 0 + + # find the closest prev block in the data + # list keys parsed as ints and in descending order + sorted_existing_blocks = sorted( + cache_copy, + reverse=True, + ) + # loop through the sorted blocks and find the closest previous block + prev_block = self.start_block + start = prev_block + bals = {} + for existing_block in sorted_existing_blocks: + if existing_block < block: + prev_block = existing_block + start = existing_block + 1 + bals = deepcopy(cache_copy[prev_block]) + break + + # parse transfer events since and update bals + while start <= block: + to_block = min(start + PAGINATION_SIZE, block) + # print(f"Fetching transfers from {start} to {to_block}") + transfers = fetch_events_logs_with_retry( + "Token transfers upsUSDe", + UPSUSDE_CONTRACT.events.Transfer(), + start, + to_block, + ) + for transfer in transfers: + recipient = transfer["args"]["to"] + sender = transfer["args"]["from"] + value = transfer["args"]["value"] / 10**18 + if recipient not in bals: + bals[recipient] = 0 + if sender not in bals: + bals[sender] = 0 + bals[recipient] += round(value, 4) + bals[sender] -= round(value, 4) + start = to_block + 1 + cache_copy[block] = bals + + converted_bals = {} + for addr, bal in bals.items(): + converted_bals[addr] = max(bal * assets_per_share, 0) + new_block_data[block] = converted_bals + + return new_block_data + + +if __name__ == "__main__": + example_integration = UpshiftupsUSDeIntegration( + integration_id=IntegrationID.UPSHIFT_UPSUSDE, + start_block=21324190, # contract deployment + summary_cols=[SummaryColumn.TEMPLATE_PTS], + chain=Chain.ETHEREUM, + reward_multiplier=20, + excluded_addresses={ZERO_ADDRESS, UPSUSDE_ADDRESS}, + end_block=None, + ) + print( + example_integration.get_block_balances( + cached_data={}, blocks=[21324292, 21324963] + ) + )