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: splitter contract #38

Merged
merged 11 commits into from
Mar 8, 2024
Merged
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
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
Loading