Skip to content

Commit

Permalink
build: override queue (#180)
Browse files Browse the repository at this point in the history
* build: override queue

* fix: cache default value

* feat: check mas loss

* chore: remane flag

* chore: rebase to merges

* fix: use max value function
  • Loading branch information
Schlagonia authored Oct 9, 2023
1 parent 0387181 commit 42e79e7
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 13 deletions.
48 changes: 35 additions & 13 deletions contracts/VaultV3.vy
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ event UpdateWithdrawLimitModule:
event UpdateDefaultQueue:
new_default_queue: DynArray[address, MAX_QUEUE]

event UpdateUseDefaultQueue:
use_default_queue: bool

event UpdatedMaxDebtForStrategy:
sender: indexed(address)
strategy: indexed(address)
Expand Down Expand Up @@ -218,6 +221,8 @@ FACTORY: public(immutable(address))
strategies: public(HashMap[address, StrategyParams])
# The current default withdrawal queue.
default_queue: public(DynArray[address, MAX_QUEUE])
# Should the vault use the default_queue regardless whats passed in.
use_default_queue: public(bool)

# ERC20 - amount of shares per account
balance_of: HashMap[address, uint256]
Expand Down Expand Up @@ -459,7 +464,7 @@ def _convert_to_assets(shares: uint256, rounding: Rounding) -> uint256:
"""
assets = shares * (total_assets / total_supply) --- (== price_per_share * shares)
"""
if shares == MAX_UINT256 or shares == 0:
if shares == max_value(uint256) or shares == 0:
return shares

total_supply: uint256 = self._total_supply()
Expand All @@ -480,7 +485,7 @@ def _convert_to_shares(assets: uint256, rounding: Rounding) -> uint256:
"""
shares = amount * (total_supply / total_assets) --- (== amount / price_per_share)
"""
if assets == MAX_UINT256 or assets == 0:
if assets == max_value(uint256) or assets == 0:
return assets

total_supply: uint256 = self._total_supply()
Expand Down Expand Up @@ -620,10 +625,13 @@ def _max_withdraw(
have: uint256 = current_idle
loss: uint256 = 0

# If no queue was passed use the default one.
_strategies: DynArray[address, MAX_QUEUE] = strategies
if len(_strategies) == 0:
_strategies = self.default_queue
# Cache the default queue.
_strategies: DynArray[address, MAX_QUEUE] = self.default_queue

# If a custom queue was passed, and we dont force the default queue.
if len(strategies) != 0 and not self.use_default_queue:
# Use the custom queue.
_strategies = strategies

for strategy in _strategies:
# Can't use an invalid strategy.
Expand Down Expand Up @@ -798,6 +806,7 @@ def _redeem(
to the user that is redeeming their vault shares.
"""
assert receiver != empty(address), "ZERO ADDRESS"
assert max_loss <= MAX_BPS, "max loss"

# If there is a withdraw limit module, check the max.
if self.withdraw_limit_module != empty(address):
Expand All @@ -822,13 +831,13 @@ def _redeem(
# funds from strategies.
if requested_assets > curr_total_idle:

# Cache the input withdrawal queue.
_strategies: DynArray[address, MAX_QUEUE] = strategies
# Cache the default queue.
_strategies: DynArray[address, MAX_QUEUE] = self.default_queue

# If no queue was passed.
if len(_strategies) == 0:
# Use the default queue.
_strategies = self.default_queue
# If a custom queue was passed, and we dont force the default queue.
if len(strategies) != 0 and not self.use_default_queue:
# Use the custom queue.
_strategies = strategies

# load to memory to save gas
curr_total_debt: uint256 = self.total_debt
Expand Down Expand Up @@ -1360,6 +1369,19 @@ def set_default_queue(new_default_queue: DynArray[address, MAX_QUEUE]):

log UpdateDefaultQueue(new_default_queue)

@external
def set_use_default_queue(use_default_queue: bool):
"""
@notice Set a new value for `use_default_queue`.
@dev If set `True` the default queue will always be
used no matter whats passed in.
@param use_default_queue new value.
"""
self._enforce_role(msg.sender, Roles.QUEUE_MANAGER)
self.use_default_queue = use_default_queue

log UpdateUseDefaultQueue(use_default_queue)

@external
def set_deposit_limit(deposit_limit: uint256):
"""
Expand All @@ -1386,7 +1408,7 @@ def set_deposit_limit_module(deposit_limit_module: address):
"""
assert self.shutdown == False # Dev: shutdown
self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)
assert self.deposit_limit == MAX_UINT256, "using deposit limit"
assert self.deposit_limit == max_value(uint256), "using deposit limit"

self.deposit_limit_module = deposit_limit_module

Expand Down
94 changes: 94 additions & 0 deletions tests/unit/vault/test_erc4626.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,53 @@ def test_max_withdraw__with_locked_strategy(
assert vault.maxWithdraw(fish.address) == assets - locked


def test_max_withdraw__with_use_default_queue(
asset,
fish,
fish_amount,
gov,
create_vault,
create_strategy,
add_debt_to_strategy,
add_strategy_to_vault,
user_deposit,
):
vault = create_vault(asset)
shares = fish_amount
assets = shares
strategy = create_strategy(vault)
strategy_deposit = assets
total_idle = assets - strategy_deposit

vault.set_role(
gov.address,
ROLES.ADD_STRATEGY_MANAGER
| ROLES.DEBT_MANAGER
| ROLES.MAX_DEBT_MANAGER
| ROLES.QUEUE_MANAGER,
sender=gov,
)
user_deposit(fish, vault, asset, assets)
add_strategy_to_vault(gov, strategy, vault)
add_debt_to_strategy(gov, strategy, vault, strategy_deposit)

assert vault.maxWithdraw(fish.address) == assets
assert vault.maxWithdraw(fish.address, 22) == assets
assert vault.maxWithdraw(fish.address, 22, [strategy]) == assets
# Using an inactive strategy will revert.
with ape.reverts("inactive strategy"):
vault.maxWithdraw(fish.address, 22, [vault])

# Set use_default_queue to true
vault.set_use_default_queue(True, sender=gov)

assert vault.maxWithdraw(fish.address) == assets
assert vault.maxWithdraw(fish.address, 22) == assets
assert vault.maxWithdraw(fish.address, 22, [strategy]) == assets
# Even sending an inactive strategy will return the correct amount.
assert vault.maxWithdraw(fish.address, 22, [vault]) == assets


def test_preview_redeem(asset, fish, fish_amount, create_vault, user_deposit):
vault = create_vault(asset)
shares = fish_amount
Expand Down Expand Up @@ -520,6 +567,53 @@ def test_max_redeem__with_locked_strategy(
assert vault.maxRedeem(fish.address) == assets - locked


def test_max_redeem__with_use_default_queue(
asset,
fish,
fish_amount,
gov,
create_vault,
create_strategy,
add_debt_to_strategy,
add_strategy_to_vault,
user_deposit,
):
vault = create_vault(asset)
shares = fish_amount
assets = shares
strategy = create_strategy(vault)
strategy_deposit = assets
total_idle = assets - strategy_deposit

vault.set_role(
gov.address,
ROLES.ADD_STRATEGY_MANAGER
| ROLES.DEBT_MANAGER
| ROLES.MAX_DEBT_MANAGER
| ROLES.QUEUE_MANAGER,
sender=gov,
)
user_deposit(fish, vault, asset, assets)
add_strategy_to_vault(gov, strategy, vault)
add_debt_to_strategy(gov, strategy, vault, strategy_deposit)

assert vault.maxRedeem(fish.address) == assets
assert vault.maxRedeem(fish.address, 22) == assets
assert vault.maxRedeem(fish.address, 22, [strategy]) == assets
# Using an inactive strategy will revert.
with ape.reverts("inactive strategy"):
vault.maxRedeem(fish.address, 22, [vault])

# Set use_default_queue to true
vault.set_use_default_queue(True, sender=gov)

assert vault.maxRedeem(fish.address) == assets
assert vault.maxRedeem(fish.address, 22) == assets
assert vault.maxRedeem(fish.address, 22, [strategy]) == assets
# Even sending an inactive strategy will return the correct amount.
assert vault.maxRedeem(fish.address, 22, [vault]) == assets


# With limit modules


Expand Down
23 changes: 23 additions & 0 deletions tests/unit/vault/test_role_base_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ def test_set_default_queue__no_queue_manager__reverts(bunny, vault):
vault.set_default_queue([], sender=bunny)


def test_use_default_queue__no_queue_manager__reverts(bunny, vault):
with ape.reverts("not allowed"):
vault.set_use_default_queue(True, sender=bunny)


def test_set_default_queue__queue_manager(gov, vault, strategy, bunny):
# We temporarily give bunny the role of DEBT_MANAGER
tx = vault.set_role(bunny.address, ROLES.QUEUE_MANAGER, sender=gov)
Expand All @@ -428,6 +433,24 @@ def test_set_default_queue__queue_manager(gov, vault, strategy, bunny):
assert vault.get_default_queue() == []


def test_set_use_default_queue__queue_manager(gov, vault, strategy, bunny):
# We temporarily give bunny the role of DEBT_MANAGER
tx = vault.set_role(bunny.address, ROLES.QUEUE_MANAGER, sender=gov)

event = list(tx.decode_logs(vault.RoleSet))
assert len(event) == 1
assert event[0].account == bunny.address
assert event[0].role == ROLES.QUEUE_MANAGER

assert vault.use_default_queue() == False
tx = vault.set_use_default_queue(True, sender=bunny)

event = list(tx.decode_logs(vault.UpdateUseDefaultQueue))
assert len(event) == 1
assert event[0].use_default_queue == True
assert vault.use_default_queue() == True


# PROFIT UNLOCK MANAGER


Expand Down
53 changes: 53 additions & 0 deletions tests/unit/vault/test_role_permissioned_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,11 @@ def test_set_default_queue__queue_manager_closed__reverts(bunny, vault):
vault.set_default_queue([], sender=bunny)


def test_set_use_default_queue__queue_manager_closed__reverts(bunny, vault):
with ape.reverts("not allowed"):
vault.set_use_default_queue(True, sender=bunny)


def test_set_default_queue__queue_manager_open(gov, vault, strategy, bunny):
# We temporarily give bunny the role of DEBT_MANAGER
tx = vault.set_open_role(ROLES.QUEUE_MANAGER, sender=gov)
Expand All @@ -826,6 +831,24 @@ def test_set_default_queue__queue_manager_open(gov, vault, strategy, bunny):
assert vault.get_default_queue() == []


def test_set_use_default_queue__queue_manager_open(gov, vault, strategy, bunny):
# We temporarily give bunny the role of DEBT_MANAGER
tx = vault.set_open_role(ROLES.QUEUE_MANAGER, sender=gov)

event = list(tx.decode_logs(vault.RoleStatusChanged))
assert len(event) == 1
assert event[0].role == ROLES.QUEUE_MANAGER
assert event[0].status == RoleStatusChange.OPENED

assert vault.use_default_queue() == False
tx = vault.set_use_default_queue(True, sender=bunny)

event = list(tx.decode_logs(vault.UpdateUseDefaultQueue))
assert len(event) == 1
assert event[0].use_default_queue == True
assert vault.use_default_queue() == True


def test_set_default_queue__queue_manager_open_then_close__reverts(
gov, vault, strategy, bunny, fish
):
Expand All @@ -850,3 +873,33 @@ def test_set_default_queue__queue_manager_open_then_close__reverts(

with ape.reverts("not allowed"):
vault.set_default_queue([], sender=fish)


def test_set_use_default_queue__queue_manager_open_then_close__reverts(
gov, vault, strategy, bunny, fish
):
# We temporarily give bunny the role of DEBT_MANAGER
tx = vault.set_open_role(ROLES.QUEUE_MANAGER, sender=gov)

event = list(tx.decode_logs(vault.RoleStatusChanged))
assert len(event) == 1
assert event[0].role == ROLES.QUEUE_MANAGER
assert event[0].status == RoleStatusChange.OPENED

assert vault.use_default_queue() == False
tx = vault.set_use_default_queue(True, sender=bunny)

event = list(tx.decode_logs(vault.UpdateUseDefaultQueue))
assert len(event) == 1
assert event[0].use_default_queue == True
assert vault.use_default_queue() == True

tx = vault.close_open_role(ROLES.QUEUE_MANAGER, sender=gov)

event = list(tx.decode_logs(vault.RoleStatusChanged))
assert len(event) == 1
assert event[0].role == ROLES.QUEUE_MANAGER
assert event[0].status == RoleStatusChange.CLOSED

with ape.reverts("not allowed"):
vault.set_use_default_queue(False, sender=fish)
Loading

0 comments on commit 42e79e7

Please sign in to comment.