Skip to content

Commit

Permalink
feat: pack pf config (#211)
Browse files Browse the repository at this point in the history
* feat: pack pf config

* chore: remove event

* chore: formatting

* test: interface updates

* chore: comments
  • Loading branch information
Schlagonia authored Sep 18, 2024
1 parent 541d75c commit 1c68d3e
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 76 deletions.
128 changes: 91 additions & 37 deletions contracts/VaultFactory.vy
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,16 @@ event UpdateGovernance:
event NewPendingGovernance:
pending_governance: indexed(address)

struct PFConfig:
# Percent of protocol's split of fees in Basis Points.
fee_bps: uint16
# Address the protocol fees get paid to.
fee_recipient: address

# Identifier for this version of the vault.
API_VERSION: constant(String[28]) = "3.0.3"

# The max amount the protocol fee can be set to.
MAX_FEE_BPS: constant(uint16) = 5_000 # 50%

# Mask used to unpack the protocol fee bps.
FEE_BPS_MASK: constant(uint256) = 2**16-1

# The address that all newly deployed vaults are based from.
VAULT_ORIGINAL: immutable(address)

Expand All @@ -92,12 +90,13 @@ pending_governance: public(address)
# Name for identification.
name: public(String[64])

# Protocol Fee Data is packed into a uint256 slot
# 72 Bits Empty | 160 Bits fee recipient | 16 bits fee bps | 8 bits custom flag

# The default config for assessing protocol fees.
default_protocol_fee_config: public(PFConfig)
default_protocol_fee_data: uint256
# Custom fee to charge for a specific vault or strategy.
custom_protocol_fee: public(HashMap[address, uint16])
# Represents if a custom protocol fee should be used.
use_custom_protocol_fee: public(HashMap[address, bool])
custom_protocol_fee_data: HashMap[address, uint256]

@external
def __init__(name: String[64], vault_original: address, governance: address):
Expand Down Expand Up @@ -163,24 +162,73 @@ def apiVersion() -> String[28]:

@view
@external
def protocol_fee_config(vault: address = msg.sender) -> PFConfig:
def protocol_fee_config(vault: address = msg.sender) -> (uint16, address):
"""
@notice Called during vault and strategy reports
to retrieve the protocol fee to charge and address
to receive the fees.
@param vault Address of the vault that would be reporting.
@return The protocol fee config for the msg sender.
@return Fee in bps
@return Address of fee recipient
"""
# If there is a custom protocol fee set we return it.
if self.use_custom_protocol_fee[vault]:
config_data: uint256 = self.custom_protocol_fee_data[vault]
if self._unpack_custom_flag(config_data):
# Always use the default fee recipient even with custom fees.
return PFConfig({
fee_bps: self.custom_protocol_fee[vault],
fee_recipient: self.default_protocol_fee_config.fee_recipient
})
return (
self._unpack_protocol_fee(config_data),
self._unpack_fee_recipient(self.default_protocol_fee_data)
)
else:
# Otherwise return the default config.
return self.default_protocol_fee_config
config_data = self.default_protocol_fee_data
return (
self._unpack_protocol_fee(config_data),
self._unpack_fee_recipient(config_data)
)

@view
@external
def use_custom_protocol_fee(vault: address) -> bool:
"""
@notice If a custom protocol fee is used for a vault.
@param vault Address of the vault to check.
@return If a custom protocol fee is used.
"""
return self._unpack_custom_flag(self.custom_protocol_fee_data[vault])

@view
@internal
def _unpack_protocol_fee(config_data: uint256) -> uint16:
"""
Unpacks the protocol fee from the packed data uint.
"""
return convert(shift(config_data, -8) & FEE_BPS_MASK, uint16)

@view
@internal
def _unpack_fee_recipient(config_data: uint256) -> address:
"""
Unpacks the fee recipient from the packed data uint.
"""
return convert(shift(config_data, -24), address)

@view
@internal
def _unpack_custom_flag(config_data: uint256) -> bool:
"""
Unpacks the custom fee flag from the packed data uint.
"""
return config_data & 1 == 1

@internal
def _pack_protocol_fee_data(recipient: address, fee: uint16, custom: bool) -> uint256:
"""
Packs the full protocol fee data into a single uint256 slot.
This is used for both the default fee storage as well as for custom fees.
72 Bits Empty | 160 Bits fee recipient | 16 bits fee bps | 8 bits custom flag
"""
return shift(convert(recipient, uint256), 24) | shift(convert(fee, uint256), 8) | convert(custom, uint256)

@external
def set_protocol_fee_bps(new_protocol_fee_bps: uint16):
Expand All @@ -194,14 +242,20 @@ def set_protocol_fee_bps(new_protocol_fee_bps: uint16):
assert new_protocol_fee_bps <= MAX_FEE_BPS, "fee too high"

# Cache the current default protocol fee.
default_config: PFConfig = self.default_protocol_fee_config
assert default_config.fee_recipient != empty(address), "no recipient"
default_fee_data: uint256 = self.default_protocol_fee_data
recipient: address = self._unpack_fee_recipient(default_fee_data)

assert recipient != empty(address), "no recipient"

# Set the new fee
self.default_protocol_fee_config.fee_bps = new_protocol_fee_bps
self.default_protocol_fee_data = self._pack_protocol_fee_data(
recipient,
new_protocol_fee_bps,
False
)

log UpdateProtocolFeeBps(
default_config.fee_bps,
self._unpack_protocol_fee(default_fee_data),
new_protocol_fee_bps
)

Expand All @@ -216,10 +270,15 @@ def set_protocol_fee_recipient(new_protocol_fee_recipient: address):
assert msg.sender == self.governance, "not governance"
assert new_protocol_fee_recipient != empty(address), "zero address"

old_recipient: address = self.default_protocol_fee_config.fee_recipient

self.default_protocol_fee_config.fee_recipient = new_protocol_fee_recipient
default_fee_data: uint256 = self.default_protocol_fee_data
old_recipient: address = self._unpack_fee_recipient(default_fee_data)

self.default_protocol_fee_data = self._pack_protocol_fee_data(
new_protocol_fee_recipient,
self._unpack_protocol_fee(default_fee_data),
False
)

log UpdateProtocolFeeRecipient(
old_recipient,
new_protocol_fee_recipient
Expand All @@ -238,14 +297,13 @@ def set_custom_protocol_fee_bps(vault: address, new_custom_protocol_fee: uint16)
"""
assert msg.sender == self.governance, "not governance"
assert new_custom_protocol_fee <= MAX_FEE_BPS, "fee too high"
assert self.default_protocol_fee_config.fee_recipient != empty(address), "no recipient"
assert self._unpack_fee_recipient(self.default_protocol_fee_data) != empty(address), "no recipient"

self.custom_protocol_fee[vault] = new_custom_protocol_fee

# If this is the first time a custom fee is set for this vault
# set the bool indicator so it returns the correct fee.
if not self.use_custom_protocol_fee[vault]:
self.use_custom_protocol_fee[vault] = True
self.custom_protocol_fee_data[vault] = self._pack_protocol_fee_data(
empty(address),
new_custom_protocol_fee,
True
)

log UpdateCustomProtocolFee(vault, new_custom_protocol_fee)

Expand All @@ -259,11 +317,8 @@ def remove_custom_protocol_fee(vault: address):
"""
assert msg.sender == self.governance, "not governance"

# Reset the custom fee to 0.
self.custom_protocol_fee[vault] = 0

# Set custom fee bool back to false.
self.use_custom_protocol_fee[vault] = False
# Reset the custom fee to 0 and flag to False.
self.custom_protocol_fee_data[vault] = self._pack_protocol_fee_data(empty(address), 0, False)

log RemovedCustomProtocolFee(vault)

Expand Down Expand Up @@ -304,4 +359,3 @@ def accept_governance():
self.pending_governance = empty(address)

log UpdateGovernance(msg.sender)

61 changes: 24 additions & 37 deletions tests/unit/factory/test_protocol_fees_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test__set_protocol_fee_recipient(gov, vault_factory):
assert event[0].old_fee_recipient == ZERO_ADDRESS
assert event[0].new_fee_recipient == gov.address

assert vault_factory.protocol_fee_config().fee_recipient == gov.address
assert vault_factory.protocol_fee_config()[1] == gov.address


def test__set_protocol_fee_recipient__zero_address__reverts(gov, vault_factory):
Expand All @@ -19,7 +19,7 @@ def test__set_protocol_fee_recipient__zero_address__reverts(gov, vault_factory):


def test__set_protocol_fees(gov, vault_factory):
assert vault_factory.protocol_fee_config().fee_bps == 0
assert vault_factory.protocol_fee_config()[0] == 0

# Need to set the fee recipient first
vault_factory.set_protocol_fee_recipient(gov.address, sender=gov)
Expand All @@ -30,26 +30,20 @@ def test__set_protocol_fees(gov, vault_factory):
assert event[0].old_fee_bps == 0
assert event[0].new_fee_bps == 20

assert vault_factory.protocol_fee_config().fee_bps == 20
assert vault_factory.protocol_fee_config()[0] == 20


def test__set_custom_protocol_fee(gov, vault_factory, create_vault, asset):
# Set the default protocol fee recipient
vault_factory.set_protocol_fee_recipient(gov.address, sender=gov)

assert vault_factory.protocol_fee_config().fee_recipient == gov.address
assert vault_factory.protocol_fee_config().fee_bps == 0
assert vault_factory.protocol_fee_config() == (0, gov.address)

vault = create_vault(asset)

# Make sure its currently set to the default settings.
assert (
vault_factory.protocol_fee_config(sender=vault.address).fee_recipient
== gov.address
)
assert vault_factory.protocol_fee_config(sender=vault.address).fee_bps == 0
assert vault_factory.protocol_fee_config(vault.address).fee_recipient == gov.address
assert vault_factory.protocol_fee_config(vault.address).fee_bps == 0
assert vault_factory.protocol_fee_config(vault.address) == (0, gov.address)
assert vault_factory.protocol_fee_config(sender=vault.address) == (0, gov.address)

new_fee = int(20)
# Set custom fee for new vault.
Expand All @@ -62,20 +56,17 @@ def test__set_custom_protocol_fee(gov, vault_factory, create_vault, asset):
assert event[0].new_custom_protocol_fee == new_fee

assert vault_factory.use_custom_protocol_fee(vault.address) == True
assert vault_factory.custom_protocol_fee(vault.address) == new_fee
assert vault_factory.protocol_fee_config(vault.address)[0] == new_fee

# Should now be different than default
assert (
vault_factory.protocol_fee_config(sender=vault.address).fee_recipient
== gov.address
assert vault_factory.protocol_fee_config(vault.address) == (new_fee, gov.address)
assert vault_factory.protocol_fee_config(sender=vault.address) == (
new_fee,
gov.address,
)
assert vault_factory.protocol_fee_config(sender=vault.address).fee_bps == new_fee
assert vault_factory.protocol_fee_config(vault.address).fee_recipient == gov.address
assert vault_factory.protocol_fee_config(vault.address).fee_bps == new_fee

# Make sure the default is not changed.
assert vault_factory.protocol_fee_config().fee_recipient == gov.address
assert vault_factory.protocol_fee_config().fee_bps == 0
assert vault_factory.protocol_fee_config() == (0, gov.address)


def test__remove_custom_protocol_fee(gov, vault_factory, create_vault, asset):
Expand All @@ -98,13 +89,11 @@ def test__remove_custom_protocol_fee(gov, vault_factory, create_vault, asset):
assert event[0].new_custom_protocol_fee == new_fee

# Should now be different than default
assert (
vault_factory.protocol_fee_config(sender=vault.address).fee_recipient
== gov.address
assert vault_factory.protocol_fee_config(vault.address) == (new_fee, gov.address)
assert vault_factory.protocol_fee_config(sender=vault.address) == (
new_fee,
gov.address,
)
assert vault_factory.protocol_fee_config(sender=vault.address).fee_bps == new_fee
assert vault_factory.protocol_fee_config(vault.address).fee_recipient == gov.address
assert vault_factory.protocol_fee_config(vault.address).fee_bps == new_fee

# Now remove the custom fee config
tx = vault_factory.remove_custom_protocol_fee(vault.address, sender=gov)
Expand All @@ -115,29 +104,27 @@ def test__remove_custom_protocol_fee(gov, vault_factory, create_vault, asset):
assert event[0].vault == vault.address

# Should now be the default
assert (
vault_factory.protocol_fee_config(sender=vault.address).fee_recipient
== gov.address
assert vault_factory.protocol_fee_config(vault.address) == (
generic_fee,
gov.address,
)
assert (
vault_factory.protocol_fee_config(sender=vault.address).fee_bps == generic_fee
assert vault_factory.protocol_fee_config(sender=vault.address) == (
generic_fee,
gov.address,
)
assert vault_factory.protocol_fee_config(vault.address).fee_recipient == gov.address
assert vault_factory.protocol_fee_config(vault.address).fee_bps == generic_fee

assert vault_factory.use_custom_protocol_fee(vault.address) == False
assert vault_factory.custom_protocol_fee(vault.address) == 0


def test__set_protocol_fee_before_recipient__reverts(gov, vault_factory):
assert vault_factory.protocol_fee_config().fee_recipient == ZERO_ADDRESS
assert vault_factory.protocol_fee_config()[1] == ZERO_ADDRESS

with ape.reverts("no recipient"):
vault_factory.set_protocol_fee_bps(20, sender=gov)


def test__set_custom_fee_before_recipient__reverts(gov, vault_factory, vault):
assert vault_factory.protocol_fee_config().fee_recipient == ZERO_ADDRESS
assert vault_factory.protocol_fee_config()[1] == ZERO_ADDRESS

with ape.reverts("no recipient"):
vault_factory.set_custom_protocol_fee_bps(vault.address, 20, sender=gov)
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/vault/test_protocol_fees.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ape import chain
import pytest
from utils.constants import ROLES, YEAR, MAX_BPS_ACCOUNTANT
from utils.constants import ROLES, YEAR, MAX_BPS_ACCOUNTANT, ZERO_ADDRESS
from utils.utils import days_to_secs


Expand All @@ -18,7 +18,7 @@ def test__report_with_no_protocol_fees__no_accountant_fees(
):
amount = fish_amount // 10

assert vault_factory.protocol_fee_config().fee_bps == 0
assert vault_factory.protocol_fee_config() == (0, ZERO_ADDRESS)

vault = create_vault(asset)
strategy = create_strategy(vault)
Expand Down

0 comments on commit 1c68d3e

Please sign in to comment.