Skip to content

Commit

Permalink
build: splitter contract (#38)
Browse files Browse the repository at this point in the history
* build: splitter contract

* feat: add events

* feat: auction option

* build: generic splitter

* fix: naming

* test: conftest

* chore: remove hh config

* test: splitter

* chore: deploy splitter

* chore: role manager script

* test: import reverts
  • Loading branch information
Schlagonia authored Mar 8, 2024
1 parent 0997196 commit 91704a3
Show file tree
Hide file tree
Showing 8 changed files with 596 additions and 17 deletions.
193 changes: 193 additions & 0 deletions contracts/splitter/Splitter.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# @version 0.3.7

interface IVault:
def asset() -> address: view
def balanceOf(owner: address) -> uint256: view
def redeem(shares: uint256, receiver: address, owner: address, max_loss: uint256) -> uint256: nonpayable
def transfer(receiver: address, amount: uint256) -> bool: nonpayable

event UpdateManagerRecipient:
newManagerRecipient: indexed(address)

event UpdateSplitee:
newSplitee: indexed(address)

event UpdateSplit:
newSplit: uint256

event UpdateMaxLoss:
newMaxLoss: uint256

event UpdateAuction:
newAuction: address

MAX_BPS: constant(uint256) = 10_000
MAX_ARRAY_SIZE: public(constant(uint256)) = 20

name: public(String[64])

# Bid daddy yankee in charge of the splitter
manager: public(address)
# Address to receive the managers shares
managerRecipient: public(address)
# Team to receive the rest of the split
splitee: public(address)

# Percent that is sent to `managerRecipient`
split: public(uint256)
# Max loss to use on vault redeems
maxLoss: public(uint256)

# Address of the contract to conduct dutch auctions for token sales
auction: public(address)

@external
def initialize(
name: String[64],
manager: address,
manager_recipient: address,
splitee: address,
original_split: uint256
):
assert self.manager == empty(address), "initialized"
assert manager != empty(address), "ZERO_ADDRESS"
assert manager_recipient != empty(address), "ZERO_ADDRESS"
assert splitee != empty(address), "ZERO_ADDRESS"
assert original_split != 0, "zero split"

self.name = name
self.manager = manager
self.managerRecipient = manager_recipient
self.splitee = splitee
self.split = original_split
self.maxLoss = 1


###### UNWRAP VAULT TOKENS ######

@external
def unwrapVault(vault: address):
assert msg.sender == self.splitee or msg.sender == self.manager, "!allowed"
self._unwrapVault(vault, self.maxLoss)

@external
def unwrapVaults(vaults: DynArray[address, MAX_ARRAY_SIZE]):
assert msg.sender == self.splitee or msg.sender == self.manager, "!allowed"

max_loss: uint256 = self.maxLoss

for vault in vaults:
self._unwrapVault(vault, max_loss)

@internal
def _unwrapVault(vault: address, max_loss: uint256):
vault_balance: uint256 = IVault(vault).balanceOf(self)
IVault(vault).redeem(vault_balance, self, self, max_loss)

###### DISTRIBUTE TOKENS ######

# split one token
@external
def distributeToken(token: address):
splitee: address = self.splitee
assert msg.sender == splitee or msg.sender == self.manager, "!allowed"
self._distribute(token, self.split, self.managerRecipient, splitee)

# split an array of tokens
@external
def distributeTokens(tokens: DynArray[address, MAX_ARRAY_SIZE]):
splitee: address = self.splitee
assert msg.sender == splitee or msg.sender == self.manager, "!allowed"

# Cache the split storage variables
split: uint256 = self.split
manager_recipient: address = self.managerRecipient

for token in tokens:
self._distribute(token, split, manager_recipient, splitee)

@internal
def _distribute(token: address, split: uint256, manager_recipient: address, splitee: address):
current_balance: uint256 = IVault(token).balanceOf(self)
manager_split: uint256 = current_balance

if split != MAX_BPS:
manager_split = unsafe_div(unsafe_mul(current_balance, split), MAX_BPS)
self._transferERC20(token, splitee, unsafe_sub(current_balance, manager_split))

self._transferERC20(token, manager_recipient, manager_split)

###### AUCTION INITIATORS ######

@external
def fundAuctions(tokens: DynArray[address, MAX_ARRAY_SIZE]):
assert msg.sender == self.splitee or msg.sender == self.manager, "!allowed"
auction: address = self.auction

for token in tokens:
amount: uint256 = IVault(token).balanceOf(self)
self._transferERC20(token, auction, amount)

@external
def fundAuction(token: address, amount: uint256 = max_value(uint256)):
assert msg.sender == self.splitee or msg.sender == self.manager, "!allowed"

to_send: uint256 = amount
if(amount == max_value(uint256)):
to_send = IVault(token).balanceOf(self)

self._transferERC20(token, self.auction, to_send)

@internal
def _transferERC20(token: address, recipient: address, amount: uint256):
# Send tokens to the auction contract.
assert IVault(token).transfer(recipient, amount, default_return_value=True), "transfer failed"

###### SETTERS ######

# update recipients
@external
def setMangerRecipient(new_recipient: address):
assert msg.sender == self.manager, "!manager"
assert new_recipient != empty(address), "ZERO_ADDRESS"

self.managerRecipient = new_recipient

log UpdateManagerRecipient(new_recipient)

@external
def setSplitee(new_splitee: address):
assert msg.sender == self.splitee, "!splitee"
assert new_splitee != empty(address), "ZERO_ADDRESS"

self.splitee = new_splitee

log UpdateSplitee(new_splitee)

# Update Split
@external
def setSplit(new_split: uint256):
assert msg.sender == self.manager, "!manager"
assert new_split != 0, "zero split"

self.split = new_split

log UpdateSplit(new_split)

# Set max loss
@external
def setMaxLoss(new_max_loss: uint256):
assert msg.sender == self.manager, "!manager"
assert new_max_loss <= MAX_BPS, "MAX_BPS"

self.maxLoss = new_max_loss

log UpdateMaxLoss(new_max_loss)

@external
def setAuction(new_auction: address):
assert msg.sender == self.manager, "!manager"

self.auction = new_auction

log UpdateAuction(new_auction)
50 changes: 50 additions & 0 deletions contracts/splitter/SplitterFactory.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# @version 0.3.7

interface ISplitter:
def initialize(
name: String[64],
manager: address,
manager_recipient: address,
splitee: address,
original_split: uint256
): nonpayable

event NewSplitter:
splitter: indexed(address)
manager: indexed(address)
manager_recipient: indexed(address)
splitee: address

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

@external
def __init__(original: address):
ORIGINAL = original


@external
def newSplitter(
name: String[64],
manager: address,
manager_recipient: address,
splitee: address,
original_split: uint256
) -> address:

# Clone a new version of the splitter
new_splitter: address = create_minimal_proxy_to(
ORIGINAL,
value=0
)

ISplitter(new_splitter).initialize(
name,
manager,
manager_recipient,
splitee,
original_split
)

log NewSplitter(new_splitter, manager, manager_recipient, splitee)
return new_splitter
16 changes: 0 additions & 16 deletions hardhat.config.js

This file was deleted.

3 changes: 2 additions & 1 deletion scripts/deploy_role_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ def deploy_role_manager():
security = input("Security? ")
keeper = input("Keeper? ")
strategy_manager = input("Strategy manager? ")
registry = input("Registry? ")

constructor = role_manager.constructor.encode_input(
gov, daddy, brain, security, keeper, strategy_manager
gov, daddy, brain, security, keeper, strategy_manager, registry
)

deploy_bytecode = HexBytes(
Expand Down
52 changes: 52 additions & 0 deletions scripts/deploy_splitter_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from ape import project, accounts, Contract, chain, networks
from hexbytes import HexBytes
from scripts.deployments import getSalt, deploy_contract


def deploy_splitter_factory():
print("Deploying Splitter Factory on ChainID", chain.chain_id)

if input("Do you want to continue? ") == "n":
return

splitter = project.Splitter
splitter_factory = project.SplitterFactory

deployer = input("Name of account to use? ")
deployer = accounts.load(deployer)

salt = getSalt("Splitter Factory")

print(f"Salt we are using {salt}")
print("Init balance:", deployer.balance / 1e18)

print(f"Deploying Original.")

original_deploy_bytecode = HexBytes(
HexBytes(splitter.contract_type.deployment_bytecode.bytecode)
)

original_address = deploy_contract(original_deploy_bytecode, salt, deployer)

print(f"Original deployed to {original_address}")

allocator_constructor = splitter_factory.constructor.encode_input(original_address)

# generate and deploy
deploy_bytecode = HexBytes(
HexBytes(splitter_factory.contract_type.deployment_bytecode.bytecode)
+ allocator_constructor
)

print(f"Deploying the Factory...")

deploy_contract(deploy_bytecode, salt, deployer)

print("------------------")
print(
f"Encoded Constructor to use for verifaction {allocator_constructor.hex()[2:]}"
)


def main():
deploy_splitter_factory()
2 changes: 2 additions & 0 deletions scripts/deployments.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ def deploy_contract(init_code, salt, deployer):

print("------------------")
print(f"Deployed the contract to {address}")

return address
29 changes: 29 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,32 @@ def role_manager(deploy_role_manager, daddy, brain, accountant, debt_allocator_f
@pytest.fixture(scope="session")
def keeper(daddy):
yield daddy.deploy(project.Keeper)


@pytest.fixture(scope="session")
def deploy_splitter_factory(project, daddy):
def deploy_splitter_factory():
original = daddy.deploy(project.Splitter)

splitter_factory = daddy.deploy(project.SplitterFactory, original)

return splitter_factory

yield deploy_splitter_factory


@pytest.fixture(scope="session")
def splitter_factory(deploy_splitter_factory):
splitter_factory = deploy_splitter_factory()
return splitter_factory


@pytest.fixture(scope="session")
def splitter(daddy, management, brain, splitter_factory):
tx = splitter_factory.newSplitter(
"Test Splitter", daddy, management, brain, 5_000, sender=daddy
)
event = list(tx.decode_logs(splitter_factory.NewSplitter))[0]
splitter = project.Splitter.at(event.splitter)

yield splitter
Loading

0 comments on commit 91704a3

Please sign in to comment.