diff --git a/hydradx/model/amm/omnipool_amm.py b/hydradx/model/amm/omnipool_amm.py index 14fc6251..e8906f82 100644 --- a/hydradx/model/amm/omnipool_amm.py +++ b/hydradx/model/amm/omnipool_amm.py @@ -912,8 +912,14 @@ def execute_add_liquidity( ) -> tuple[OmnipoolState, Agent]: """Compute new state after liquidity addition""" + if quantity <= 0: + return state.fail_transaction('Quantity must be non-negative.', agent) + delta_Q = lrna_price(state, tkn_add) * quantity - if not (state.unique_id, tkn_add) in agent.holdings: + if (state.unique_id, tkn_add) in agent.holdings: + if agent.holdings[(state.unique_id, tkn_add)] != 0: + return state.fail_transaction(f'Agent already has liquidity in pool {tkn_add}.', agent) + else: agent.holdings[(state.unique_id, tkn_add)] = 0 if agent.holdings[tkn_add] < quantity: diff --git a/hydradx/tests/test_omnipool_amm.py b/hydradx/tests/test_omnipool_amm.py index a22743ce..516be408 100644 --- a/hydradx/tests/test_omnipool_amm.py +++ b/hydradx/tests/test_omnipool_amm.py @@ -125,6 +125,40 @@ def test_add_liquidity(initial_state: oamm.OmnipoolState): raise AssertionError(f'legal transaction failed against weight limit in {i} ({new_state.fail})') +@settings(max_examples=1) +@given(omnipool_config(token_count=3, lrna_fee=0, asset_fee=0)) +def test_add_liquidity_with_existing_position_fails(initial_state: oamm.OmnipoolState): + old_state = initial_state + tkn = old_state.asset_list[0] + old_agent = Agent( + holdings={tkn: old_state.liquidity[tkn] / 10, (old_state.unique_id, tkn): old_state.shares[tkn] / 10} + ) + + delta_R = old_agent.holdings[tkn] + + new_state, new_agents = oamm.add_liquidity(old_state, old_agent, delta_R, tkn) + + if not new_state.fail: + raise AssertionError(f'Adding liquidity to an existing position should fail.') + + +@settings(max_examples=1) +@given(omnipool_config(token_count=3, lrna_fee=0, asset_fee=0)) +def test_add_liquidity_with_quantity_zero_should_fail(initial_state: oamm.OmnipoolState): + old_state = initial_state + tkn = old_state.asset_list[0] + old_agent = Agent( + holdings={tkn: old_state.liquidity[tkn] / 10, (old_state.unique_id, tkn): old_state.shares[tkn] / 10} + ) + + delta_R = 0 + + new_state, new_agents = oamm.add_liquidity(old_state, old_agent, delta_R, tkn) + + if not new_state.fail: + raise AssertionError(f'Adding liquidity with quantity zero should fail.') + + @given(omnipool_config(token_count=3, withdrawal_fee=False)) def test_remove_liquidity_no_fee(initial_state: oamm.OmnipoolState): i = initial_state.asset_list[2] @@ -1545,52 +1579,56 @@ def test_volatility_limit(omnipool: oamm.OmnipoolState): def test_LP_limits(omnipool: oamm.OmnipoolState, max_withdrawal_per_block, max_lp_per_block): omnipool.max_withdrawal_per_block = max_withdrawal_per_block omnipool.max_lp_per_block = max_lp_per_block - agent = Agent(holdings={'HDX': 10000000000}) + state = omnipool.copy() + initial_agent = Agent(holdings={'HDX': 10000000000}) + agent = initial_agent.copy() oamm.execute_add_liquidity( - state=omnipool, + state=state, agent=agent, tkn_add='HDX', - quantity=omnipool.liquidity['HDX'] * max_lp_per_block + quantity=state.liquidity['HDX'] * max_lp_per_block ) - if omnipool.fail: + if state.fail: raise AssertionError('Valid LP operation failed.') - omnipool.update() + state = omnipool.copy() + agent = initial_agent.copy() oamm.execute_add_liquidity( - state=omnipool, + state=state, agent=agent, tkn_add='HDX', - quantity=omnipool.liquidity['HDX'] * max_lp_per_block + 1 + quantity=state.liquidity['HDX'] * max_lp_per_block + 1 ) - if not omnipool.fail: + if not state.fail: raise AssertionError('Invalid LP operation succeeded.') - omnipool.update() + state = omnipool.copy() + agent = initial_agent.copy() # add liquidity again to test remove liquidity oamm.execute_add_liquidity( - state=omnipool, + state=state, agent=agent, tkn_add='HDX', - quantity=omnipool.liquidity['HDX'] * max_lp_per_block + quantity=state.liquidity['HDX'] * max_lp_per_block ) - if omnipool.fail: + if state.fail: raise AssertionError('Second LP operation failed.') withdraw_quantity = agent.holdings[('omnipool', 'HDX')] - total_shares = omnipool.shares['HDX'] + total_shares = state.shares['HDX'] oamm.execute_remove_liquidity( - state=omnipool, + state=state, agent=agent, tkn_remove='HDX', quantity=withdraw_quantity # agent.holdings[('omnipool', 'HDX')] ) - if withdraw_quantity / total_shares > max_withdrawal_per_block and not omnipool.fail: + if withdraw_quantity / total_shares > max_withdrawal_per_block and not state.fail: raise AssertionError('Agent was able to remove too much liquidity.') - omnipool.update() + state.update() oamm.execute_remove_liquidity( - state=omnipool, + state=state, agent=agent, tkn_remove='HDX', - quantity=omnipool.shares['HDX'] * max_withdrawal_per_block + quantity=state.shares['HDX'] * max_withdrawal_per_block ) - if omnipool.fail: + if state.fail: raise AssertionError('Agent was not able to remove liquidity.')