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

build: modules #202

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ jobs:
with:
fetch-depth: 1

- name: Set up python 3.8
- name: Set up python 3.10
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.10

- name: Set pip cache directory path
id: pip-cache-dir-path
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

steps:
- uses: actions/checkout@v1
- uses: ApeWorX/github-action@v2.0
- uses: ApeWorX/github-action
with:
python-version: '3.10'

Expand All @@ -34,7 +34,7 @@ jobs:
- name: Setup node.js
uses: actions/setup-node@v1
with:
node-version: '16.x'
node-version: '18.x'

- name: Install hardhat
run: npm install hardhat
Expand Down
119 changes: 56 additions & 63 deletions contracts/VaultV3.vy
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ interface IStrategy:
interface IAccountant:
def report(strategy: address, gain: uint256, loss: uint256) -> (uint256, uint256): nonpayable

interface IDepositLimitModule:
def available_deposit_limit(receiver: address) -> uint256: view
interface IModule:
def availableDepositLimit(receiver: address) -> uint256: view
def postDeposit(assets: uint256, shares: uint256, sender: address, receiver: address): nonpayable
def availableWithdrawLimit(owner: address, max_loss: uint256, strategies: DynArray[address, MAX_QUEUE]) -> uint256: view
def postWithdraw(assets: uint256, shares: uint256, sender: address, owner: address, receiver: address): nonpayable
def preTransfer(sender: address, receiver: address, shares: uint256): nonpayable

interface IWithdrawLimitModule:
def available_withdraw_limit(owner: address, max_loss: uint256, strategies: DynArray[address, MAX_QUEUE]) -> uint256: view

interface IFactory:
def protocol_fee_config() -> (uint16, address): view

Expand Down Expand Up @@ -118,11 +119,9 @@ event UpdateRoleManager:
event UpdateAccountant:
accountant: indexed(address)

event UpdateDepositLimitModule:
deposit_limit_module: indexed(address)

event UpdateWithdrawLimitModule:
withdraw_limit_module: indexed(address)
event UpdateModule:
module: indexed(Modules)
contract: indexed(address)

event UpdateDefaultQueue:
new_default_queue: DynArray[address, MAX_QUEUE]
Expand Down Expand Up @@ -191,6 +190,14 @@ enum Roles:
PROFIT_UNLOCK_MANAGER # Sets the profit_max_unlock_time.
DEBT_PURCHASER # Can purchase bad debt from the vault.
EMERGENCY_MANAGER # Can shutdown vault in an emergency.
MODULE_MANAGER # Can set and update hooks.

enum Modules:
DEPOSIT_LIMIT
WITHDRAW_LIMIT
DEPOSIT_HOOK
WITHDRAW_HOOK
TRANSFER_HOOK

enum StrategyChangeType:
ADDED
Expand Down Expand Up @@ -234,10 +241,8 @@ deposit_limit: public(uint256)
### PERIPHERY ###
# Contract that charges fees and can give refunds.
accountant: public(address)
# Contract to control the deposit limit.
deposit_limit_module: public(address)
# Contract to control the withdraw limit.
withdraw_limit_module: public(address)

modules: public(HashMap[Modules, address])

### ROLES ###
# HashMap mapping addresses to their roles
Expand Down Expand Up @@ -329,6 +334,10 @@ def _spend_allowance(owner: address, spender: address, amount: uint256):
def _transfer(sender: address, receiver: address, amount: uint256):
sender_balance: uint256 = self.balance_of[sender]
assert sender_balance >= amount, "insufficient funds"
transfer_module: address = self.modules[Modules.TRANSFER_HOOK]
if transfer_module != empty(address):
IModule(transfer_module).preTransfer(sender, receiver, amount)

self.balance_of[sender] = unsafe_sub(sender_balance, amount)
self.balance_of[receiver] = unsafe_add(self.balance_of[receiver], amount)
log Transfer(sender, receiver, amount)
Expand Down Expand Up @@ -532,9 +541,9 @@ def _max_deposit(receiver: address) -> uint256:
return 0

# If there is a deposit limit module set use that.
deposit_limit_module: address = self.deposit_limit_module
deposit_limit_module: address = self.modules[Modules.DEPOSIT_LIMIT]
if deposit_limit_module != empty(address):
return IDepositLimitModule(deposit_limit_module).available_deposit_limit(receiver)
return IModule(deposit_limit_module).availableDepositLimit(receiver)

# Else use the standard flow.
_deposit_limit: uint256 = self.deposit_limit
Expand Down Expand Up @@ -574,12 +583,12 @@ def _max_withdraw(
max_assets: uint256 = self._convert_to_assets(self.balance_of[owner], Rounding.ROUND_DOWN)

# If there is a withdraw limit module use that.
withdraw_limit_module: address = self.withdraw_limit_module
withdraw_limit_module: address = self.modules[Modules.WITHDRAW_LIMIT]
if withdraw_limit_module != empty(address):
return min(
# Use the min between the returned value and the max.
# Means the limit module doesn't need to account for balances or conversions.
IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies),
IModule(withdraw_limit_module).availableWithdrawLimit(owner, max_loss, strategies),
max_assets
)

Expand Down Expand Up @@ -676,6 +685,11 @@ def _deposit(sender: address, recipient: address, assets: uint256) -> uint256:
assert shares > 0, "cannot mint zero"

log Deposit(sender, recipient, assets, shares)

post_deposit_module: address = self.modules[Modules.DEPOSIT_HOOK]
if post_deposit_module != empty(address):
IModule(post_deposit_module).postDeposit(assets, shares, msg.sender, recipient)

return shares

@internal
Expand All @@ -701,6 +715,11 @@ def _mint(sender: address, recipient: address, shares: uint256) -> uint256:
self._issue_shares(shares, recipient)

log Deposit(sender, recipient, assets, shares)

post_deposit_module: address = self.modules[Modules.DEPOSIT_HOOK]
if post_deposit_module != empty(address):
IModule(post_deposit_module).postDeposit(assets, shares, msg.sender, recipient)

return assets

@view
Expand Down Expand Up @@ -782,9 +801,9 @@ def _redeem(
assert max_loss <= MAX_BPS, "max loss"

# If there is a withdraw limit module, check the max.
withdraw_limit_module: address = self.withdraw_limit_module
withdraw_limit_module: address = self.modules[Modules.WITHDRAW_LIMIT]
if withdraw_limit_module != empty(address):
assert assets <= IWithdrawLimitModule(withdraw_limit_module).available_withdraw_limit(owner, max_loss, strategies), "exceed withdraw limit"
assert assets <= IModule(withdraw_limit_module).availableWithdrawLimit(owner, max_loss, strategies), "exceed withdraw limit"

assert self.balance_of[owner] >= shares, "insufficient shares to redeem"

Expand Down Expand Up @@ -946,6 +965,11 @@ def _redeem(
self._erc20_safe_transfer(_asset, receiver, requested_assets)

log Withdraw(sender, receiver, owner, requested_assets, shares)

post_withdraw_module: address = self.modules[Modules.WITHDRAW_HOOK]
if post_withdraw_module != empty(address):
IModule(post_withdraw_module).postWithdraw(assets, shares, sender, owner, receiver)

return requested_assets

## STRATEGY MANAGEMENT ##
Expand Down Expand Up @@ -1397,58 +1421,27 @@ def set_deposit_limit(deposit_limit: uint256, override: bool = False):
# If we are overriding the deposit limit module.
if override:
# Make sure it is set to address 0 if not already.
if self.deposit_limit_module != empty(address):
if self.modules[Modules.DEPOSIT_LIMIT] != empty(address):

self.modules[Modules.DEPOSIT_LIMIT] = empty(address)

self.deposit_limit_module = empty(address)
log UpdateDepositLimitModule(empty(address))
log UpdateModule(Modules.DEPOSIT_LIMIT, empty(address))
else:
# Make sure the deposit_limit_module has been set to address(0).
assert self.deposit_limit_module == empty(address), "using module"
assert self.modules[Modules.DEPOSIT_LIMIT] == empty(address), "using module"

self.deposit_limit = deposit_limit

log UpdateDepositLimit(deposit_limit)

@external
def set_deposit_limit_module(deposit_limit_module: address, override: bool = False):
"""
@notice Set a contract to handle the deposit limit.
@dev The default `deposit_limit` will need to be set to
max uint256 since the module will override it or the override flag
must be set to true to set it to max in 1 tx..
@param deposit_limit_module Address of the module.
@param override If a `deposit_limit` already set should be overridden.
"""
assert self.shutdown == False # Dev: shutdown
self._enforce_role(msg.sender, Roles.DEPOSIT_LIMIT_MANAGER)

# If we are overriding the deposit limit
if override:
# Make sure it is max uint256 if not already.
if self.deposit_limit != max_value(uint256):

self.deposit_limit = max_value(uint256)
log UpdateDepositLimit(max_value(uint256))
else:
# Make sure the deposit_limit has been set to uint max.
assert self.deposit_limit == max_value(uint256), "using deposit limit"

self.deposit_limit_module = deposit_limit_module

log UpdateDepositLimitModule(deposit_limit_module)
def set_module(module: Modules, contract: address):
self._enforce_role(msg.sender, Roles.MODULE_MANAGER)

@external
def set_withdraw_limit_module(withdraw_limit_module: address):
"""
@notice Set a contract to handle the withdraw limit.
@dev This will override the default `max_withdraw`.
@param withdraw_limit_module Address of the module.
"""
self._enforce_role(msg.sender, Roles.WITHDRAW_LIMIT_MANAGER)
self.modules[module] = contract

self.withdraw_limit_module = withdraw_limit_module
log UpdateModule(module, contract)

log UpdateWithdrawLimitModule(withdraw_limit_module)

@external
def set_minimum_total_idle(minimum_total_idle: uint256):
Expand Down Expand Up @@ -1744,10 +1737,10 @@ def shutdown_vault():
self.shutdown = True

# Set deposit limit to 0.
if self.deposit_limit_module != empty(address):
self.deposit_limit_module = empty(address)
if self.modules[Modules.DEPOSIT_LIMIT] != empty(address):
self.modules[Modules.DEPOSIT_LIMIT] = empty(address)

log UpdateDepositLimitModule(empty(address))
log UpdateModule(Modules.DEPOSIT_LIMIT, empty(address))

self.deposit_limit = 0
log UpdateDepositLimit(0)
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/mocks/periphery/LimitModule.vy
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(

@view
@external
def available_deposit_limit(receiver: address) -> uint256:
def availableDepositLimit(receiver: address) -> uint256:
if self.enforce_whitelist:
if not self.whitelist[receiver]:
return 0
Expand All @@ -35,7 +35,7 @@ def available_deposit_limit(receiver: address) -> uint256:

@view
@external
def available_withdraw_limit(owner: address, max_loss: uint256, strategies: DynArray[address, 10]) -> uint256:
def availableWithdrawLimit(owner: address, max_loss: uint256, strategies: DynArray[address, 10]) -> uint256:
return self.default_withdraw_limit

@external
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
black==22.3.0
eth-ape>=0.7.0
eth-ape>=0.8.0
vyper==0.3.7
Loading