Skip to content

Commit

Permalink
feat: pack pf config
Browse files Browse the repository at this point in the history
  • Loading branch information
Schlagonia committed Sep 12, 2024
1 parent 5bd1034 commit 42868e8
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 73 deletions.
109 changes: 75 additions & 34 deletions contracts/VaultFactory.vy
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,15 @@ 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%

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 +89,13 @@ pending_governance: public(address)
# Name for identification.
name: public(String[64])

# Protocol Fee Data is packed into a uint256
# 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,7 +161,7 @@ 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
Expand All @@ -172,15 +170,60 @@ def protocol_fee_config(vault: address = msg.sender) -> PFConfig:
@return The protocol fee config for the msg sender.
"""
# If there is a custom protocol fee set we return it.
if self.use_custom_protocol_fee[vault]:
custom_data: uint256 = self.custom_protocol_fee_data[vault]
if custom_data & 1 == 1:
# 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(custom_data),
self._unpack_fee_recipient(self.default_protocol_fee_data)
)
else:
# Otherwise return the default config.
return self.default_protocol_fee_config
return self._default_protocol_fee_config(self.default_protocol_fee_data)

@view
@external
def protocol_fee_recipient() -> address:
return self._unpack_fee_recipient(self.default_protocol_fee_data)

@view
@external
def protocol_fee(vault: address = msg.sender) -> uint16:
return self._unpack_protocol_fee(self.default_protocol_fee_data)

@view
@external
def use_custom_protocol_fee(vault: address) -> bool:
return self.custom_protocol_fee_data[vault] & 1 == 1

@view
@external
def custom_protocol_fee(vault: address) -> uint16:
return self._unpack_protocol_fee(self.custom_protocol_fee_data[vault])

@view
@internal
def _default_protocol_fee_config(config_data: uint256) -> (uint16, address):
fee: uint16 = self._unpack_protocol_fee(config_data)
recipient: address = self._unpack_fee_recipient(config_data)
return (fee, recipient)

@view
@internal
def _unpack_protocol_fee(config_data: uint256) -> uint16:
return convert(shift(config_data, -8) & FEE_BPS_MASK, uint16)

@view
@internal
def _unpack_fee_recipient(config_data: uint256) -> address:
return convert(shift(config_data, -24), address)

@internal
def _pack_data(recipient: address, fee: uint16, custom: bool) -> uint256:
return shift(convert(recipient, uint256), 24) | shift(convert(fee, uint256), 8) | convert(custom, uint256)

event ConfigData:
data: uint256

@external
def set_protocol_fee_bps(new_protocol_fee_bps: uint16):
Expand All @@ -194,14 +237,18 @@ 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_data(recipient, new_protocol_fee_bps, False)

log ConfigData(self.default_protocol_fee_data)

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

Expand All @@ -216,9 +263,12 @@ 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
default_fee_data: uint256 = self.default_protocol_fee_data
old_recipient: address = self._unpack_fee_recipient(default_fee_data)

self.default_protocol_fee_config.fee_recipient = new_protocol_fee_recipient
self.default_protocol_fee_data = self._pack_data(new_protocol_fee_recipient, self._unpack_protocol_fee(default_fee_data), False)

log ConfigData(self.default_protocol_fee_data)

log UpdateProtocolFeeRecipient(
old_recipient,
Expand All @@ -238,14 +288,9 @@ 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_data(empty(address), new_custom_protocol_fee, True)

log UpdateCustomProtocolFee(vault, new_custom_protocol_fee)

Expand All @@ -260,10 +305,7 @@ 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
self.custom_protocol_fee_data[vault] = self._pack_data(empty(address), 0, False)

log RemovedCustomProtocolFee(vault)

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

log UpdateGovernance(msg.sender)

54 changes: 15 additions & 39 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 @@ -65,17 +59,11 @@ def test__set_custom_protocol_fee(gov, vault_factory, create_vault, asset):
assert vault_factory.custom_protocol_fee(vault.address) == 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(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
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)

# 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 +86,8 @@ 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(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
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)

# Now remove the custom fee config
tx = vault_factory.remove_custom_protocol_fee(vault.address, sender=gov)
Expand All @@ -115,29 +98,22 @@ 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(sender=vault.address).fee_bps == generic_fee
)
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.protocol_fee_config(vault.address) == (generic_fee, gov.address)
assert vault_factory.protocol_fee_config(sender=vault.address) == (generic_fee, gov.address)

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

0 comments on commit 42868e8

Please sign in to comment.