Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix-140: ensure user cannot intentionally create bad debt #13

Merged
merged 1 commit into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions contracts/margin-dex/Vault.vy
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ def open_position(
assert self.is_whitelisted_dex[msg.sender], "unauthorized"
assert self.is_enabled_market[_debt_token][_position_token], "market not enabled"
assert self.margin[_account][_debt_token] >= _margin_amount, "not enough margin"
assert ((_debt_amount + _margin_amount) * PRECISION / _margin_amount) <= self.max_leverage[_debt_token][_position_token] * PRECISION, "too much leverage"
assert (self._available_liquidity(_debt_token) >= _debt_amount), "insufficient liquidity"
assert self._available_liquidity(_debt_token) >= _debt_amount, "insufficient liquidity"

self.margin[_account][_debt_token] -= _margin_amount
debt_shares: uint256 = self._borrow(_debt_token, _debt_amount)
Expand Down Expand Up @@ -205,6 +204,8 @@ def open_position(
self.margin[_account][_debt_token] -= fee
self._distribute_trading_fee(_debt_token, fee)

assert not self._is_liquidatable(position_uid), "cannot open liquidatable position"

log PositionOpened(_account, position)

return position_uid, amount_bought
Expand All @@ -227,6 +228,7 @@ event BadDebt:
@external
def close_position(_position_uid: bytes32, _min_amount_out: uint256) -> uint256:
assert self.is_whitelisted_dex[msg.sender], "unauthorized"
assert _min_amount_out >= self._debt(_position_uid), "invalid min_amount_out"
return self._close_position(_position_uid, _min_amount_out)


Expand Down Expand Up @@ -336,6 +338,8 @@ def reduce_position(

self.positions[_position_uid] = position

assert not self._is_liquidatable(_position_uid), "cannot reduce into liquidation"

log PositionReduced(position.account, _position_uid, position, amount_out_received)

return amount_out_received
Expand Down
14 changes: 7 additions & 7 deletions tests/vault/test_bad_debt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def setup(vault, mock_router, owner, weth, usdc):
def open_position(vault, owner, weth, usdc):
margin_amount = 15 * 10**6
usdc_in = 150 * 10**6
min_weth_out = 123
min_weth_out = 1 * 10**18

uid, amount_bought = vault.open_position(
owner, # account
Expand All @@ -28,22 +28,22 @@ def open_position(vault, owner, weth, usdc):


def test_repay_bad_debt_001(vault, usdc, weth, owner):
"""when bad-debt is repayd it should be reduced in the vault,
"""when bad-debt is repaid it should be reduced in the vault,
the available-liquidity should go up correspondentingly"""
bad_debt_before = vault.bad_debt(usdc)
position_uid, _ = open_position(vault, owner, weth, usdc)
bad_debt_before = vault.bad_debt(usdc)
assert bad_debt_before == 0

vault.positions(position_uid)
vault.close_position(position_uid, 0) # 0 out
vault.internal._close_position(position_uid, 1) # 1 wei out
bad_debt_after = vault.bad_debt(usdc)

assert bad_debt_after == 150 * 10**6
assert vault.available_liquidity(usdc) == 999700000000
assert bad_debt_after == 150 * 10**6 - 1
assert vault.available_liquidity(usdc) == 999700000002

vault.repay_bad_debt(usdc, 150 * 10**6)
vault.repay_bad_debt(usdc, bad_debt_after)
bad_debt_after_repaying = vault.bad_debt(usdc)
assert bad_debt_after_repaying == 0

assert vault.available_liquidity(usdc) == 999700000000 + 150 * 10**6
assert vault.available_liquidity(usdc) == 999700000002 + bad_debt_after
31 changes: 16 additions & 15 deletions tests/vault/test_close_position.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def setup(vault, mock_router, owner, weth, usdc):


def open_position(vault, owner, weth, usdc):
margin_amount = 15 * 10**6
usdc_in = 150 * 10**6
min_weth_out = 123
margin_amount = 150 * 10**6
usdc_in = 1500 * 10**6
min_weth_out = 3 * 10**18

uid, amount_bought = vault.open_position(
owner, # account
Expand Down Expand Up @@ -42,7 +42,7 @@ def test_close_position_reduces_position_to_zero(vault, owner, weth, usdc):

assert position_before[6] > 0 # [6] = postion_amount

vault.close_position(position_uid, 150 * 10**6)
vault.close_position(position_uid, 1500 * 10**6)
position_after = vault.positions(position_uid)

assert position_after[6] == 0 # [6] = position_amount
Expand All @@ -53,8 +53,8 @@ def test_close_position_swaps_liquidity_for_underlying(vault, weth, usdc, owner)
liquidity_before = usdc.balanceOf(vault)
underlying_before = weth.balanceOf(vault)

weth_in = 123
min_usdc_out = 150 * 10**6
weth_in = 3 * 10**18
min_usdc_out = 1500 * 10**6
vault.close_position(position_uid, min_usdc_out)

liquidity_after = usdc.balanceOf(vault)
Expand All @@ -71,16 +71,16 @@ def test_close_position_in_profit_increases_margin(vault, weth, usdc, owner):

position = vault.positions(position_uid)
position_margin = position[3]
assert position_margin == 15 * 10**6
assert position_margin == 150 * 10**6

min_usdc_out = (
250 * 10**6
1750 * 10**6
) # simulated profit of 100 in addition to initial (margin + borrowed)
vault.close_position(position_uid, min_usdc_out)

margin_after = vault.margin(owner, usdc)

expected_pnl = 85 * 10**6
expected_pnl = 100 * 10**6
assert (
margin_after == account_margin_after_trade_open + position_margin + expected_pnl
)
Expand All @@ -93,9 +93,9 @@ def test_close_position_in_loss_reduces_margin(vault, usdc, weth, owner):
position = vault.positions(position_uid)
position_margin = position[3]

# position-supply = debt + margin = 150 + 15
# position-supply = debt + margin = 1500 + 150
expected_loss = 10 * 10**6
min_usdc_out = 165 * 10**6 - expected_loss # simulated loss of 10
min_usdc_out = 1650 * 10**6 - expected_loss # simulated loss of 10
vault.close_position(position_uid, min_usdc_out)

margin_after = vault.margin(owner, usdc)
Expand Down Expand Up @@ -144,24 +144,25 @@ def test_close_position_in_bad_debt_records_bad_debt(vault, usdc, weth, owner):
assert bad_debt_before == 0

vault.positions(position_uid)
vault.close_position(position_uid, 0) # 0 out
vault.internal._close_position(position_uid, 1) # 1 wei out
bad_debt_after = vault.bad_debt(usdc)

# TODO ensure position-debt is here the correct value
# we are minting new bad-debt shares and it is the first
# position, thus all share-positions should now be bad-debt
# that means all the 150 that where borrowed are now bad debt
assert bad_debt_after == 150 * 10**6
# that means all the 1500 that where borrowed are now bad debt
assert bad_debt_after == 1500 * 10**6 - 1


def test_close_position_in_bad_debt_deactivates_accepting_new_orders(
vault, usdc, weth, owner
):
position_uid, _ = open_position(vault, owner, weth, usdc)
assert vault.is_accepting_new_orders()
assert vault.acceptable_amount_of_bad_debt(usdc) == 0
vault.positions(position_uid)

vault.close_position(position_uid, 0) # 0 out
vault.internal._close_position(position_uid, 1) # 0 out

bad_debt_after = vault.bad_debt(usdc)

Expand Down
20 changes: 10 additions & 10 deletions tests/vault/test_open_position.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ def test_open_trade_swaps_liquidity_for_underlying(vault, owner, weth, usdc):
liquidity_before = usdc.balanceOf(vault)
underlying_before = weth.balanceOf(vault)

margin_amount = 15 * 10**6
min_borrow_amount = 150 * 10**6
min_position_amount = 123
margin_amount = 150 * 10**6
min_borrow_amount = 1500 * 10**6
min_position_amount = 3 * 10**18
vault.open_position(
owner, weth, min_position_amount, usdc, min_borrow_amount, margin_amount
)
Expand All @@ -156,9 +156,9 @@ def test_open_trade_swaps_liquidity_for_underlying(vault, owner, weth, usdc):
def test_open_trade_reduces_free_margin_correctly(vault, owner, usdc, weth, alice):
margin_before = vault.margin(owner, usdc)

margin_amount = 15 * 10**6
min_borrow_amount = 150 * 10**6
min_position_amount = 123
margin_amount = 150 * 10**6
min_borrow_amount = 1500 * 10**6
min_position_amount = 3 * 10**18
vault.open_position(
owner, weth, min_position_amount, usdc, min_borrow_amount, margin_amount
)
Expand All @@ -172,9 +172,9 @@ def test_open_trade_reduces_free_margin_correctly(vault, owner, usdc, weth, alic
def test_open_position_records_amount_lent_out_correctly(vault, owner, usdc, weth):
debt_before = vault.total_debt_amount(usdc)

margin_amount = 15 * 10**6
min_borrow_amount = 150 * 10**6
min_position_amount = 123
margin_amount = 150 * 10**6
min_borrow_amount = 1500 * 10**6
min_position_amount = 3 * 10**18
vault.open_position(
owner, weth, min_position_amount, usdc, min_borrow_amount, margin_amount
)
Expand Down Expand Up @@ -204,7 +204,7 @@ def test_cannot_open_trade_with_higher_than_max_leverage(vault, owner, weth, usd
min_borrow_amount = margin_amount * max_leverage + 1
min_position_amount = 123

with boa.reverts("too much leverage"):
with boa.reverts("cannot open liquidatable position"):
vault.open_position(
owner, weth, min_position_amount, usdc, min_borrow_amount, margin_amount
)
Expand Down