Open scherrey opened 1 month ago
Vyper 0.3.10 and titanoboa @ git+https://github.com/vyperlang/titanoboa@a06e134b25c8206cb4d6d76521e6705111e92c68
In the unit test, test_allocate_balance_adapter_tx, passing the tuple representation of the Vyper struct to the @external method works fine but passing it to the @internal method fails. See execution result at bottom.
test_yield_bearing_asset_funds_allocator.py
import pytest import boa from decimal import Decimal from dataclasses import dataclass, field # ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" # MAX_ADAPTERS = 5 # Must match the value from AdapterVault.vy @pytest.fixture def deployer(): acc = boa.env.generate_address(alias="deployer") boa.env.set_balance(acc, 1000*10**18) return acc # @pytest.fixture # def trader(): # acc = boa.env.generate_address(alias="trader") # boa.env.set_balance(acc, 1000*10**18) # return acc # @pytest.fixture # def dai(deployer, trader): # with boa.env.prank(deployer): # erc = boa.load("contracts/test_helpers/ERC20.vy", "DAI Token", "DAI", 18, 1000*10**18, deployer) # erc.mint(deployer, 100000) # erc.mint(trader, 100000) # return erc # @pytest.fixture # def erc20(deployer, trader): # with boa.env.prank(deployer): # erc = boa.load("contracts/test_helpers/ERC20.vy", "ERC20", "Coin", 18, 1000*10**18, deployer) # erc.mint(deployer, 100000) # erc.mint(trader, 100000) # return erc @pytest.fixture def funds_alloc(deployer): with boa.env.prank(deployer): f = boa.load("contracts/YieldBearingAssetFundsAllocator.vy") return f def test_is_full_rebalance(funds_alloc): assert funds_alloc.internal._is_full_rebalance() == False max_uint256 = 2**256 - 1 max_int256 = 2**255 - 1 min_uint256 = 0 min_int256 = -2**255 neutral_max_deposit = max_int256 - 42 @dataclass class BalanceAdapter: adapter: str current: int = field(default=0) last_value: int = field(default=0) max_deposit: int = field(default=max_int256) max_withdraw: int = field(default=min_int256) ratio: int = field(default=0) target: int = field(default=0) delta: int = field(default=0) @classmethod def from_dict(cls, data: dict): return cls(**data) def to_tuple(self): return ( self.adapter, self.current, self.last_value, self.max_deposit, self.max_withdraw, self.ratio, self.target, self.delta ) balance_adapters_data = [ { 'adapter': '0x0000000000000000000000000000000000000001', 'current': 1000, 'last_value': 900, 'ratio': 10 }, { 'adapter': '0x0000000000000000000000000000000000000002', 'current': 2000, 'last_value': 1800, 'max_deposit': 1000, 'max_withdraw': -600, 'ratio': 20, 'target' : 2000, 'delta' : 0 }, ] def test_allocate_balance_adapter_tx(funds_alloc): adapter = BalanceAdapter.from_dict(balance_adapters_data[0]) adapter_tuple = adapter.to_tuple() result = funds_alloc.allocate_balance_adapter_tx(100, adapter_tuple) result = funds_alloc.internal._allocate_balance_adapter_tx(100, adapter_tuple) # This fails with boa now. adapter.target = 100 * adapter.ratio adapter.delta = adapter.target - adapter.current assert result == (adapter.to_tuple(), 0, False, False)
YieldBearingAssetFundsAllocator.vy
#pragma evm-version cancun """ @title Adapter Fund Allocation Logic @license Copyright 2023, 2024 Biggest Lab Co Ltd, Benjamin Scherrey, Sajal Kayan, and Eike Caldeweyher @author BiggestLab (https://biggestlab.io) Benjamin Scherrey """ #interface AdapterVault: # pass ## ## Must match AdapterVault.vy ## MAX_ADAPTERS : constant(uint256) = 5 ADAPTER_BREAKS_LOSS_POINT : constant(decimal) = 0.05 # This structure must match definition in AdapterVault.vy struct BalanceTX: qty: int256 adapter: address # This structure must match definition in AdapterVault.vy struct BalanceAdapter: adapter: address current: uint256 last_value: uint256 max_deposit: int256 max_withdraw: int256 # represented as a negative number ratio: uint256 target: uint256 delta: int256 @external @view def getBalanceTxs(_vault_balance: uint256, _target_asset_balance: uint256, _min_proposer_payout: uint256, _total_assets: uint256, _total_ratios: uint256, _adapter_states: BalanceAdapter[MAX_ADAPTERS], _withdraw_only : bool = False) -> (BalanceTX[MAX_ADAPTERS], address[MAX_ADAPTERS]): return self._getBalanceTxs(_vault_balance, _target_asset_balance, _min_proposer_payout, _total_assets, _total_ratios, _adapter_states, _withdraw_only ) @internal @pure def _getBalanceTxs(_vault_balance: uint256, _target_asset_balance: uint256, _min_proposer_payout: uint256, _total_assets: uint256, _total_ratios: uint256, _adapter_states: BalanceAdapter[MAX_ADAPTERS], _withdraw_only : bool = False) -> (BalanceTX[MAX_ADAPTERS], address[MAX_ADAPTERS]): # _BDM TODO : max_txs is ignored for now. adapter_txs : BalanceTX[MAX_ADAPTERS] = empty(BalanceTX[MAX_ADAPTERS]) blocked_adapters : address[MAX_ADAPTERS] = empty(address[MAX_ADAPTERS]) adapter_states: BalanceAdapter[MAX_ADAPTERS] = empty(BalanceAdapter[MAX_ADAPTERS]) d4626_delta : int256 = 0 tx_count : uint256 = 0 #d4626_delta, tx_count, adapter_states, blocked_adapters = self._getTargetBalances(_vault_balance, _target_asset_balance, _total_assets, _total_ratios, _adapter_states, _min_proposer_payout, _withdraw_only) pos : uint256 = 0 for tx_bal in adapter_states: adapter_txs[pos] = BalanceTX({qty: tx_bal.delta, adapter: tx_bal.adapter}) pos += 1 return adapter_txs, blocked_adapters @internal @view def _is_full_rebalance() -> bool: return False NEUTRAL_ADAPTER_MAX_DEPOSIT : constant(int256) = max_value(int256) - 42 @internal @pure def _allocate_balance_adapter_tx(_ratio_value : uint256, _balance_adapter : BalanceAdapter) -> (BalanceAdapter, int256, bool, bool): """ Given a value per strategy ratio and an un-allocated BalanceAdapter, return the newly allocated BalanceAdapter constrained by min & max limits and also identify if this adapter should be blocked due to unexpected losses, plus identify whether or not this is our "neutral adapter". """ is_neutral_adapter : bool = _balance_adapter.max_deposit == NEUTRAL_ADAPTER_MAX_DEPOSIT # Have funds been lost? should_we_block_adapter : bool = False if _balance_adapter.current < _balance_adapter.last_value: _balance_adapter.ratio = 0 should_we_block_adapter = True target : uint256 = _ratio_value * _balance_adapter.ratio delta : int256 = convert(_balance_adapter.current, int256) - convert(target, int256) leftovers : int256 = 0 # Limit deposits to max_deposit if delta > _balance_adapter.max_deposit: leftovers = _balance_adapter.max_deposit - delta delta = _balance_adapter.max_deposit # Limit withdraws to max_withdraw elif delta < _balance_adapter.max_withdraw: leftovers = delta - _balance_adapter.max_withdraw delta = _balance_adapter.max_withdraw _balance_adapter.delta = delta _balance_adapter.target = target # We are not adjusting the optimium target for now. return _balance_adapter, leftovers, should_we_block_adapter, is_neutral_adapter @external @pure def allocate_balance_adapter_tx(_ratio_value : uint256, _balance_adapter : BalanceAdapter) -> (BalanceAdapter, int256, bool, bool): return self._allocate_balance_adapter_tx(_ratio_value, _balance_adapter)
script execution:
pytest tests_boa/ --ignore tests_boa/test_transient.py tests_boa/test_yield_bearing_asset_funds_allocator.py ============================================================================== test session starts =============================================================================== platform linux -- Python 3.11.2, pytest-8.2.1, pluggy-1.5.0 rootdir: /home/scherrey/projects/adapter-fi/AdapterVault plugins: hypothesis-6.103.0, cov-5.0.0, titanoboa-0.1.10b1, web3-6.11.0 collected 2 items tests_boa/test_yield_bearing_asset_funds_allocator.py .F [100%] ==================================================================================== FAILURES ==================================================================================== ________________________________________________________________________ test_allocate_balance_adapter_tx ________________________________________________________________________ source_code = '\n@external\n@payable\ndef __boa_private__allocate_balance_adapter_tx__(_ratio_value: uint256, _balance_adapter: Bala...claration object, int256, bool, bool):\n return self._allocate_balance_adapter_tx(_ratio_value, _balance_adapter)\n' source_id = {}, contract_name = None, add_fn_node = None def parse_to_ast_with_settings( source_code: str, source_id: int = 0, contract_name: Optional[str] = None, add_fn_node: Optional[str] = None, ) -> tuple[Settings, vy_ast.Module]: """ Parses a Vyper source string and generates basic Vyper AST nodes. Parameters ---------- source_code : str The Vyper source code to parse. source_id : int, optional Source id to use in the `src` member of each node. contract_name: str, optional Name of contract. add_fn_node: str, optional If not None, adds a dummy Python AST FunctionDef wrapper node. Returns ------- list Untyped, unoptimized Vyper AST nodes. """ if "\x00" in source_code: raise ParserException("No null bytes (\\x00) allowed in the source code.") settings, class_types, reformatted_code = pre_parse(source_code) try: > py_ast = python_ast.parse(reformatted_code) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/vyper/ast/utils.py:44: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ source = '\n@external\n@payable\ndef __boa_private__allocate_balance_adapter_tx__(_ratio_value: uint256, _balance_adapter: Bala...claration object, int256, bool, bool):\n return self._allocate_balance_adapter_tx(_ratio_value, _balance_adapter)\n' filename = '<unknown>', mode = 'exec' def parse(source, filename='<unknown>', mode='exec', *, type_comments=False, feature_version=None): """ Parse the source into an AST node. Equivalent to compile(source, filename, mode, PyCF_ONLY_AST). Pass type_comments=True to get back type comments where the syntax allows. """ flags = PyCF_ONLY_AST if type_comments: flags |= PyCF_TYPE_COMMENTS if isinstance(feature_version, tuple): major, minor = feature_version # Should be a 2-tuple. assert major == 3 feature_version = minor elif feature_version is None: feature_version = -1 # Else it should be an int giving the minor version for 3.x. > return compile(source, filename, mode, flags, _feature_version=feature_version) E File "<unknown>", line 4 E def __boa_private__allocate_balance_adapter_tx__(_ratio_value: uint256, _balance_adapter: BalanceAdapter declaration object) -> (BalanceAdapter declaration object, int256, bool, bool): E ^^^^^^^^^^^^^^^^^^^^^^^^^^ E SyntaxError: invalid syntax. Perhaps you forgot a comma? /usr/lib/python3.11/ast.py:50: SyntaxError The above exception was the direct cause of the following exception: funds_alloc = <contracts/YieldBearingAssetFundsAllocator.vy at 0x8A369A3a3a60866B01ABB5b30D3Cce00F06b98F2, compiled with vyper-0.3.10+9136169> def test_allocate_balance_adapter_tx(funds_alloc): adapter = BalanceAdapter.from_dict(balance_adapters_data[0]) adapter_tuple = adapter.to_tuple() result = funds_alloc.allocate_balance_adapter_tx(100, adapter_tuple) > result = funds_alloc.internal._allocate_balance_adapter_tx(100, adapter_tuple) # This fails with boa now. tests_boa/test_yield_bearing_asset_funds_allocator.py:106: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/boa/contracts/vyper/vyper_contract.py:1022: in __call__ if hasattr(self, "_ir_executor"): /usr/lib/python3.11/functools.py:1001: in __get__ val = self.func(instance) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/boa/contracts/vyper/vyper_contract.py:1065: in _ir_executor _, ir_executor, _, _, _ = self._compiled /usr/lib/python3.11/functools.py:1001: in __get__ val = self.func(instance) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/boa/contracts/vyper/vyper_contract.py:1055: in _compiled return generate_bytecode_for_internal_fn(self) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/boa/contracts/vyper/compiler_utils.py:115: in generate_bytecode_for_internal_fn return compile_vyper_function(wrapper_code, contract) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/boa/contracts/vyper/compiler_utils.py:41: in compile_vyper_function ast = parse_to_ast(vyper_function, ifaces) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/vyper/ast/utils.py:12: in parse_to_ast return parse_to_ast_with_settings(*args, **kwargs)[1] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ source_code = '\n@external\n@payable\ndef __boa_private__allocate_balance_adapter_tx__(_ratio_value: uint256, _balance_adapter: Bala...claration object, int256, bool, bool):\n return self._allocate_balance_adapter_tx(_ratio_value, _balance_adapter)\n' source_id = {}, contract_name = None, add_fn_node = None def parse_to_ast_with_settings( source_code: str, source_id: int = 0, contract_name: Optional[str] = None, add_fn_node: Optional[str] = None, ) -> tuple[Settings, vy_ast.Module]: """ Parses a Vyper source string and generates basic Vyper AST nodes. Parameters ---------- source_code : str The Vyper source code to parse. source_id : int, optional Source id to use in the `src` member of each node. contract_name: str, optional Name of contract. add_fn_node: str, optional If not None, adds a dummy Python AST FunctionDef wrapper node. Returns ------- list Untyped, unoptimized Vyper AST nodes. """ if "\x00" in source_code: raise ParserException("No null bytes (\\x00) allowed in the source code.") settings, class_types, reformatted_code = pre_parse(source_code) try: py_ast = python_ast.parse(reformatted_code) except SyntaxError as e: # TODO: Ensure 1-to-1 match of source_code:reformatted_code SyntaxErrors > raise SyntaxException(str(e), source_code, e.lineno, e.offset) from e E vyper.exceptions.SyntaxException: invalid syntax. Perhaps you forgot a comma? (<unknown>, line 4) E line 4:91 E 3 @payable E ---> 4 def __boa_private__allocate_balance_adapter_tx__(_ratio_value: uint256, _balance_adapter: BalanceAdapter declaration object) -> (BalanceAdapter declaration object, int256, bool, bool): E --------------------------------------------------------------------------------------------------^ E 5 return self._allocate_balance_adapter_tx(_ratio_value, _balance_adapter) ../../../.virtualenvs/AdapterBoa/lib/python3.11/site-packages/vyper/ast/utils.py:47: SyntaxException ============================================================================ short test summary info ============================================================================= FAILED tests_boa/test_yield_bearing_asset_funds_allocator.py::test_allocate_balance_adapter_tx - vyper.exceptions.SyntaxException: invalid syntax. Perhaps you forgot a comma? (<unknown>, line 4) ========================================================================== 1 failed, 1 passed in 0.35s =========================================================================== make: *** [Makefile:41: test] Error 1
Vyper 0.3.10 and titanoboa @ git+https://github.com/vyperlang/titanoboa@a06e134b25c8206cb4d6d76521e6705111e92c68
In the unit test, test_allocate_balance_adapter_tx, passing the tuple representation of the Vyper struct to the @external method works fine but passing it to the @internal method fails. See execution result at bottom.
test_yield_bearing_asset_funds_allocator.py
YieldBearingAssetFundsAllocator.vy
script execution: