Skip to content

Commit

Permalink
Valuation test fix for Uniswap v2
Browse files Browse the repository at this point in the history
  • Loading branch information
miohtama committed Dec 24, 2024
1 parent 33dfe56 commit ae613f6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 27 deletions.
30 changes: 29 additions & 1 deletion eth_defi/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import re
from functools import lru_cache
from pathlib import Path
from typing import Optional, Sequence, Type, Union
from typing import Optional, Sequence, Type, Union, Any

import eth_abi
from eth_abi import decode
Expand Down Expand Up @@ -296,6 +296,34 @@ def encode_function_args(func: ContractFunction, args: Sequence) -> bytes:
return encoded_args


def decode_function_output(func: ContractFunction, data: bytes) -> Any:
"""Decode raw return value of Solidity function using Contract proxy object.
Uses `web3.Contract.functions` prepared function as the ABI source.
:param func:
Function which arguments we are going to encode.
Must be bound.
:param result:
Raw encoded Solidity bytes.
"""
assert isinstance(func, ContractFunction)

web3 = func.w3

fn_abi, fn_selector, aligned_fn_arguments = get_function_info(
func.fn_name,
web3.codec,
func.contract_abi,
args=func.args,
)
arg_types = [t["type"] for t in fn_abi["outputs"]]
decoded_out = eth_abi.decode(arg_types, data)
return decoded_out


def encode_function_call(
func: ContractFunction,
args: Sequence,
Expand Down
18 changes: 3 additions & 15 deletions eth_defi/event_reader/multicall_batcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def get_key(self) -> Hashable:
"""Get key that will identify this call in the result dictionary"""

@abstractmethod
def handle(self, succeed: bool, raw_return_value: Any) -> Any:
def handle(self, succeed: bool, raw_return_value: bytes) -> Any:
"""Parse the call result.
:param succeed:
Expand Down Expand Up @@ -285,22 +285,10 @@ def multicall_callback(self, succeed: bool, raw_return_value: Any) -> Any:
# Avoid expensive logging if we do not need it
if self.debug:
# Print calldata so we can copy-paste it to Tenderly for symbolic debug stack trace
data = self.get_data()
call = self.create_multicall()
logger.info("Path did not success: %s on %s, selector %s",
self,
self.signature_string,
call.signature.fourbyte.hex(),
)
logger.info("Arguments: %s", self.signature[1:])
logger.info(
"Contract: %s\nCalldata: %s",
self.contract_address,
data.hex()
)
address, data = self.get_address_and_data()
logger.info("Calldata failed %s: %s", address, data)
try:
value = self.handle(succeed, raw_return_value)

except Exception as e:
logger.error(
"Handler failed %s for return value %s",
Expand Down
25 changes: 18 additions & 7 deletions eth_defi/vault/valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from web3 import Web3
from web3.contract import Contract

from eth_defi.abi import decode_function_output
from eth_defi.event_reader.multicall_batcher import get_multicall_contract, call_multicall_batched_single_thread, MulticallWrapper, call_multicall_debug_single_thread
from eth_defi.provider.anvil import is_mainnet_fork
from eth_defi.provider.broken_provider import get_almost_latest_block_number
Expand Down Expand Up @@ -186,7 +187,7 @@ def create_multicall(self) -> Call:
call = Call(self.contract_address, self.signature, [(self.route, self)])
return call

def handle(self, success, raw_return_value) -> TokenAmount | None:
def handle(self, success, raw_return_value: bytes) -> TokenAmount | None:

if not success:
return None
Expand All @@ -197,7 +198,7 @@ def handle(self, success, raw_return_value) -> TokenAmount | None:
raw_return_value,
)
except Exception as e:
raise RuntimeError(f"Filed to decode. Quoter {self.quoter}, return dadta {raw_return_value}") from e
raise RuntimeError(f"Failed to decode. Quoter {self.quoter}, return dadta {raw_return_value}") from e
return token_amount


Expand Down Expand Up @@ -319,13 +320,23 @@ def generate_routes(
def handle_onchain_return_value(
self,
wrapper: ValuationMulticallWrapper,
raw_return_value: any,
raw_return_value: bytes,
) -> Decimal | None:
"""Convert swapExactTokensForTokens() return value to tokens we receive"""
"""Convert getAmountsOut() return value to tokens we receive"""
route = wrapper.route
target_token_out = raw_return_value[-1]
logger.info("Uniswap V2, resolved %s to %s", route.get_formatted_path(), target_token_out)
return route.target_token.convert_to_decimals(target_token_out)
func = self.swap_router_v2.functions.getAmountsOut(wrapper.amount_in, wrapper.route.address_path)
decoded = decode_function_output(func, raw_return_value)
target_token_out = decoded[0][-1]
human_out = route.target_token.convert_to_decimals(target_token_out)
logger.info(
"Uniswap V2, path %s resolved, %s %s -> %s %s",
route.get_formatted_path(),
route.source_token.convert_to_decimals(wrapper.amount_in),
route.source_token.symbol,
human_out,
route.target_token.symbol
)
return human_out

def get_path_combinations(
self,
Expand Down
10 changes: 6 additions & 4 deletions tests/lagoon/test_lagoon_valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,10 @@ def test_lagoon_diagnose_routes(
print()
print(routes)

assert routes.loc["USDC"]["Value"] is not None
assert routes.loc["WETH -> USDC"]["Value"] is not None
assert routes.loc["DINO -> WETH -> USDC"]["Value"] is not None
assert routes.loc["DINO -> USDC"]["Value"] == "-"
assert routes.loc[routes["Path"] == "USDC"]["Value"] is not None
assert routes.loc[routes["Path"] == "WETH -> USDC"]["Value"] is not None
assert routes.loc[routes["Path"] == "DINO -> WETH -> USDC"]["Value"] is not None
assert routes.loc[routes["Path"] == "DINO -> USDC"]["Value"].iloc[0] == "-"


def test_lagoon_post_valuation(
Expand Down Expand Up @@ -445,6 +445,8 @@ def test_lagoon_post_valuation(

# First post the new valuation as valuation manager
total_value = portfolio_valuation.get_total_equity()
assert total_value > 10 # 0.30 USDC

bound_func = vault.post_new_valuation(total_value)
tx_hash = bound_func.transact({"from": valuation_manager}) # Unlocked by anvil
assert_transaction_success_with_explanation(web3, tx_hash)
Expand Down

0 comments on commit ae613f6

Please sign in to comment.