From 6a68ec02ed8d6afbcee1c136fb7277549543a558 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 13 Feb 2024 18:27:43 -0700 Subject: [PATCH 01/11] build: splitter contract --- contracts/splitter/SpliiterFactory.vy | 49 ++++++++++ contracts/splitter/Splitter.vy | 131 ++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 contracts/splitter/SpliiterFactory.vy create mode 100644 contracts/splitter/Splitter.vy diff --git a/contracts/splitter/SpliiterFactory.vy b/contracts/splitter/SpliiterFactory.vy new file mode 100644 index 0000000..0969804 --- /dev/null +++ b/contracts/splitter/SpliiterFactory.vy @@ -0,0 +1,49 @@ +# @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) + splitee: indexed(address) + +# The address that all newly deployed vaults are based from. +ORIGINAL: 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 vault using create2. + 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, splitee) + return new_splitter \ No newline at end of file diff --git a/contracts/splitter/Splitter.vy b/contracts/splitter/Splitter.vy new file mode 100644 index 0000000..81ac52a --- /dev/null +++ b/contracts/splitter/Splitter.vy @@ -0,0 +1,131 @@ +# @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 + +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) + +@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 <= MAX_BPS, "MAX_BPS" + 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 = unsafe_div(unsafe_mul(current_balance, split), MAX_BPS) + assert IVault(token).transfer(manager_recipient, manager_split, default_return_value=True), "transfer failed" + assert IVault(token).transfer(splitee, unsafe_sub(current_balance, manager_split), default_return_value=True), "transfer failed" + +###### SETTERS ###### + +# Update Split +@external +def setSplit(new_split: uint256): + assert msg.sender == self.manager, "!manager" + assert new_split <= MAX_BPS, "MAX_BPS" + assert new_split != 0, "zero split" + + self.split = new_split + +# 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 + +@external +def setSplitee(new_splitee: address): + assert msg.sender == self.splitee, "!splitee" + assert new_splitee != empty(address), "ZERO_ADDRESS" + + self.splitee = new_splitee + +# 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 From 24c1d579745e409ec5cd02443a4e684b8fb411d3 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 13 Feb 2024 18:49:21 -0700 Subject: [PATCH 02/11] feat: add events --- contracts/splitter/SpliiterFactory.vy | 5 ++-- contracts/splitter/Splitter.vy | 40 ++++++++++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/contracts/splitter/SpliiterFactory.vy b/contracts/splitter/SpliiterFactory.vy index 0969804..384daab 100644 --- a/contracts/splitter/SpliiterFactory.vy +++ b/contracts/splitter/SpliiterFactory.vy @@ -12,7 +12,8 @@ interface ISplitter: event NewSplitter: splitter: indexed(address) manager: indexed(address) - splitee: indexed(address) + manager_recipient: indexed(address) + splitee: address # The address that all newly deployed vaults are based from. ORIGINAL: immutable(address) @@ -45,5 +46,5 @@ def newSplitter( original_split ) - log NewSplitter(new_splitter, manager, splitee) + log NewSplitter(new_splitter, manager, manager_recipient, splitee) return new_splitter \ No newline at end of file diff --git a/contracts/splitter/Splitter.vy b/contracts/splitter/Splitter.vy index 81ac52a..44d2592 100644 --- a/contracts/splitter/Splitter.vy +++ b/contracts/splitter/Splitter.vy @@ -6,6 +6,18 @@ interface IVault: 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 + MAX_BPS: constant(uint256) = 10_000 MAX_ARRAY_SIZE: public(constant(uint256)) = 20 @@ -46,7 +58,7 @@ def initialize( self.maxLoss = 1 -####### UNWRAP VAULT TOKENS ###### +###### UNWRAP VAULT TOKENS ###### @external def unwrapVault(vault: address): @@ -98,15 +110,6 @@ def _distribute(token: address, split: uint256, manager_recipient: address, spli ###### SETTERS ###### -# Update Split -@external -def setSplit(new_split: uint256): - assert msg.sender == self.manager, "!manager" - assert new_split <= MAX_BPS, "MAX_BPS" - assert new_split != 0, "zero split" - - self.split = new_split - # update recipients @external def setMangerRecipient(new_recipient: address): @@ -115,6 +118,8 @@ def setMangerRecipient(new_recipient: address): self.managerRecipient = new_recipient + log UpdateManagerRecipient(new_recipient) + @external def setSplitee(new_splitee: address): assert msg.sender == self.splitee, "!splitee" @@ -122,6 +127,19 @@ def setSplitee(new_splitee: 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 <= MAX_BPS, "MAX_BPS" + assert new_split != 0, "zero split" + + self.split = new_split + + log UpdateSplit(new_split) + # Set max loss @external def setMaxLoss(new_max_loss: uint256): @@ -129,3 +147,5 @@ def setMaxLoss(new_max_loss: uint256): assert new_max_loss <= MAX_BPS, "MAX_BPS" self.maxLoss = new_max_loss + + log UpdateMaxLoss(new_max_loss) From 477700915912d4ccc405b1772fc399c422b2550d Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 14 Feb 2024 17:14:24 -0700 Subject: [PATCH 03/11] feat: auction option --- contracts/splitter/Splitter.vy | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/contracts/splitter/Splitter.vy b/contracts/splitter/Splitter.vy index 44d2592..2742d58 100644 --- a/contracts/splitter/Splitter.vy +++ b/contracts/splitter/Splitter.vy @@ -6,6 +6,10 @@ interface IVault: def redeem(shares: uint256, receiver: address, owner: address, max_loss: uint256) -> uint256: nonpayable def transfer(receiver: address, amount: uint256) -> bool: nonpayable +interface IAuction: + def getAuctionId(from_token: address) -> bytes32: view + def auctionInfo(auction_id: bytes32) -> (address, address, uint256, uint256): view + event UpdateManagerRecipient: newManagerRecipient: indexed(address) @@ -18,6 +22,9 @@ event UpdateSplit: event UpdateMaxLoss: newMaxLoss: uint256 +event UpdateAuction: + newAuction: address + MAX_BPS: constant(uint256) = 10_000 MAX_ARRAY_SIZE: public(constant(uint256)) = 20 @@ -35,6 +42,9 @@ 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], @@ -108,6 +118,37 @@ def _distribute(token: address, split: uint256, manager_recipient: address, spli assert IVault(token).transfer(manager_recipient, manager_split, default_return_value=True), "transfer failed" assert IVault(token).transfer(splitee, unsafe_sub(current_balance, manager_split), default_return_value=True), "transfer failed" + +###### 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._fundAuction(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._fundAuction(token, self.auction, to_send) + +@internal +def _fundAuction(token: address, auction: address, amount: uint256): + # Make sure the auction is set is enabled for this token + from_token: address = IAuction(auction).auctionInfo(IAuction(auction).getAuctionId(token))[0] + assert from_token == token, "not enabled" + + # Send tokens to the auction contract. + assert IVault(token).transfer(auction, amount, default_return_value=True), "transfer failed" + ###### SETTERS ###### # update recipients @@ -149,3 +190,9 @@ def setMaxLoss(new_max_loss: uint256): 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 \ No newline at end of file From ac2e024dcd95b9aedc2779f0d2da4775f116cbaa Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 26 Feb 2024 16:54:13 -0700 Subject: [PATCH 04/11] build: generic splitter --- contracts/splitter/SpliiterFactory.vy | 2 +- contracts/splitter/Splitter.vy | 27 +++++++++--------------- tests/conftest.py | 30 +++++++++++++++++++++++++++ tests/splitter/test_splitter.py | 14 +++++++++++++ 4 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 tests/splitter/test_splitter.py diff --git a/contracts/splitter/SpliiterFactory.vy b/contracts/splitter/SpliiterFactory.vy index 384daab..336cc22 100644 --- a/contracts/splitter/SpliiterFactory.vy +++ b/contracts/splitter/SpliiterFactory.vy @@ -32,7 +32,7 @@ def newSplitter( original_split: uint256 ) -> address: - # Clone a new version of the vault using create2. + # Clone a new version of the splitter new_splitter: address = create_minimal_proxy_to( ORIGINAL, value=0 diff --git a/contracts/splitter/Splitter.vy b/contracts/splitter/Splitter.vy index 2742d58..8e461aa 100644 --- a/contracts/splitter/Splitter.vy +++ b/contracts/splitter/Splitter.vy @@ -6,10 +6,6 @@ interface IVault: def redeem(shares: uint256, receiver: address, owner: address, max_loss: uint256) -> uint256: nonpayable def transfer(receiver: address, amount: uint256) -> bool: nonpayable -interface IAuction: - def getAuctionId(from_token: address) -> bytes32: view - def auctionInfo(auction_id: bytes32) -> (address, address, uint256, uint256): view - event UpdateManagerRecipient: newManagerRecipient: indexed(address) @@ -57,7 +53,6 @@ def initialize( assert manager != empty(address), "ZERO_ADDRESS" assert manager_recipient != empty(address), "ZERO_ADDRESS" assert splitee != empty(address), "ZERO_ADDRESS" - assert original_split <= MAX_BPS, "MAX_BPS" assert original_split != 0, "zero split" self.name = name @@ -114,10 +109,13 @@ def distributeTokens(tokens: DynArray[address, MAX_ARRAY_SIZE]): @internal def _distribute(token: address, split: uint256, manager_recipient: address, splitee: address): current_balance: uint256 = IVault(token).balanceOf(self) - manager_split: uint256 = unsafe_div(unsafe_mul(current_balance, split), MAX_BPS) - assert IVault(token).transfer(manager_recipient, manager_split, default_return_value=True), "transfer failed" - assert IVault(token).transfer(splitee, unsafe_sub(current_balance, manager_split), default_return_value=True), "transfer failed" + 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 ###### @@ -128,7 +126,7 @@ def fundAuctions(tokens: DynArray[address, MAX_ARRAY_SIZE]): for token in tokens: amount: uint256 = IVault(token).balanceOf(self) - self._fundAuction(token, auction, amount) + self._transferERC20(token, auction, amount) @external def fundAuction(token: address, amount: uint256 = max_value(uint256)): @@ -138,16 +136,12 @@ def fundAuction(token: address, amount: uint256 = max_value(uint256)): if(amount == max_value(uint256)): to_send = IVault(token).balanceOf(self) - self._fundAuction(token, self.auction, to_send) + self._transferERC20(token, self.auction, to_send) @internal -def _fundAuction(token: address, auction: address, amount: uint256): - # Make sure the auction is set is enabled for this token - from_token: address = IAuction(auction).auctionInfo(IAuction(auction).getAuctionId(token))[0] - assert from_token == token, "not enabled" - +def _transferERC20(token: address, recipient: address, amount: uint256): # Send tokens to the auction contract. - assert IVault(token).transfer(auction, amount, default_return_value=True), "transfer failed" + assert IVault(token).transfer(recipient, amount, default_return_value=True), "transfer failed" ###### SETTERS ###### @@ -174,7 +168,6 @@ def setSplitee(new_splitee: address): @external def setSplit(new_split: uint256): assert msg.sender == self.manager, "!manager" - assert new_split <= MAX_BPS, "MAX_BPS" assert new_split != 0, "zero split" self.split = new_split diff --git a/tests/conftest.py b/tests/conftest.py index 9431f84..4fed5aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -433,3 +433,33 @@ 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): + def splitter(gov=daddy, recipient=brain, splitte=management, split=5_000): + splitter = splitter_factory.newSplitter( + "Test Splitter", gov, recipient, splitte, split + ) + + return splitter + + yield splitter diff --git a/tests/splitter/test_splitter.py b/tests/splitter/test_splitter.py new file mode 100644 index 0000000..b8bd628 --- /dev/null +++ b/tests/splitter/test_splitter.py @@ -0,0 +1,14 @@ +import ape +from ape import chain +from utils.constants import ZERO_ADDRESS, MAX_INT + + +def test_split_setup(splitter_factory, splitter, daddy, brain, management): + assert splitter_factory.ORIGINAL() != ZERO_ADDRESS + assert splitter.address != ZERO_ADDRESS + assert splitter.manager() == daddy + assert splitter.managerRecipient() == brain + assert splitter.splitee() == management + assert splitter.split() == 5_000 + assert splitter.maxLoss() == 1 + assert splitter.auction() == ZERO_ADDRESS From ac0a001e75418433a761eef8ca115fb5dcf90266 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 27 Feb 2024 14:46:54 -0700 Subject: [PATCH 05/11] fix: naming --- .../splitter/{SpliiterFactory.vy => SplitterFactory.vy} | 2 +- tests/conftest.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) rename contracts/splitter/{SpliiterFactory.vy => SplitterFactory.vy} (96%) diff --git a/contracts/splitter/SpliiterFactory.vy b/contracts/splitter/SplitterFactory.vy similarity index 96% rename from contracts/splitter/SpliiterFactory.vy rename to contracts/splitter/SplitterFactory.vy index 336cc22..0300c80 100644 --- a/contracts/splitter/SpliiterFactory.vy +++ b/contracts/splitter/SplitterFactory.vy @@ -16,7 +16,7 @@ event NewSplitter: splitee: address # The address that all newly deployed vaults are based from. -ORIGINAL: immutable(address) +ORIGINAL: public(immutable(address)) @external def __init__(original: address): diff --git a/tests/conftest.py b/tests/conftest.py index 4fed5aa..b2422ae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -455,11 +455,10 @@ def splitter_factory(deploy_splitter_factory): @pytest.fixture(scope="session") def splitter(daddy, management, brain, splitter_factory): - def splitter(gov=daddy, recipient=brain, splitte=management, split=5_000): - splitter = splitter_factory.newSplitter( - "Test Splitter", gov, recipient, splitte, split - ) + splitter = splitter_factory.newSplitter( + "Test Splitter", daddy, management, brain, 5_000, sender=daddy + ) - return splitter + splitter = project.Splitter.at(splitter) yield splitter From b800b65874d4acd667693091720d9df758ed9844 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 27 Feb 2024 16:01:22 -0700 Subject: [PATCH 06/11] test: conftest --- tests/conftest.py | 6 +++--- tests/splitter/test_splitter.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b2422ae..bf93a02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -455,10 +455,10 @@ def splitter_factory(deploy_splitter_factory): @pytest.fixture(scope="session") def splitter(daddy, management, brain, splitter_factory): - splitter = splitter_factory.newSplitter( + tx = splitter_factory.newSplitter( "Test Splitter", daddy, management, brain, 5_000, sender=daddy ) - - splitter = project.Splitter.at(splitter) + event = list(tx.decode_logs(splitter_factory.NewSplitter))[0] + splitter = project.Splitter.at(event.splitter) yield splitter diff --git a/tests/splitter/test_splitter.py b/tests/splitter/test_splitter.py index b8bd628..4b2b7fa 100644 --- a/tests/splitter/test_splitter.py +++ b/tests/splitter/test_splitter.py @@ -7,8 +7,8 @@ def test_split_setup(splitter_factory, splitter, daddy, brain, management): assert splitter_factory.ORIGINAL() != ZERO_ADDRESS assert splitter.address != ZERO_ADDRESS assert splitter.manager() == daddy - assert splitter.managerRecipient() == brain - assert splitter.splitee() == management + assert splitter.managerRecipient() == management + assert splitter.splitee() == brain assert splitter.split() == 5_000 assert splitter.maxLoss() == 1 assert splitter.auction() == ZERO_ADDRESS From c38a92c2aca4e8939d3466600f684dc693c75a40 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 4 Mar 2024 10:26:24 -0700 Subject: [PATCH 07/11] chore: remove hh config --- hardhat.config.js | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 hardhat.config.js diff --git a/hardhat.config.js b/hardhat.config.js deleted file mode 100644 index 16366e8..0000000 --- a/hardhat.config.js +++ /dev/null @@ -1,16 +0,0 @@ - -// See https://hardhat.org/config/ for config options. -module.exports = { - networks: { - hardhat: { - hardfork: "london", - // Base fee of 0 allows use of 0 gas price when testing - initialBaseFeePerGas: 0, - accounts: { - mnemonic: "test test test test test test test test test test test junk", - path: "m/44'/60'/0'", - count: 10 - } - }, - }, - }; \ No newline at end of file From a8c1a9721e215a72598ca8839b996196a2c50907 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 7 Mar 2024 05:34:36 -0700 Subject: [PATCH 08/11] test: splitter --- tests/splitter/test_splitter.py | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/splitter/test_splitter.py b/tests/splitter/test_splitter.py index 4b2b7fa..a3aa1fd 100644 --- a/tests/splitter/test_splitter.py +++ b/tests/splitter/test_splitter.py @@ -12,3 +12,43 @@ def test_split_setup(splitter_factory, splitter, daddy, brain, management): assert splitter.split() == 5_000 assert splitter.maxLoss() == 1 assert splitter.auction() == ZERO_ADDRESS + +def test_unwrap(splitter, daddy, vault, mock_tokenized, strategy, asset, user, amount): + assert splitter.manager() == daddy + + amount = amount // 3 + + asset.approve(vault, amount, sender=user) + asset.approve(mock_tokenized, amount, sender=user) + asset.approv(strategy, amount, sender=user) + + vault.deposit(amount, splitter, sender=user) + mock_tokenized.deposit(amount, splitter, sender=user) + strategy.deposit(amount, splitter, sender=user) + + assert vault.balanceOf(splitter) == amount + assert mock_tokenized.balanceOf(splitter) == amount + assert strategy.balanceOf(splitter) == amount + assert asset.balanceOf(splitter) == 0 + + with ape.reverts("!allowed"): + splitter.unwrapVault(strategy, sender=user) + + splitter.unwrapVault(strategy, sender=daddy) + + assert vault.balanceOf(splitter) == 0 + assert mock_tokenized.balanceOf(splitter) == amount + assert strategy.balanceOf(splitter) == amount + assert asset.balanceOf(splitter) == amount + + vaults = [vault, mock_tokenized] + + with ape.reverts("!allowed"): + splitter.unwrapVaults(vaults, sender=user) + + splitter.unwrapVaults(vaults, sender=daddy) + + assert vault.balanceOf(splitter) == 0 + assert mock_tokenized.balanceOf(splitter) == 0 + assert strategy.balanceOf(splitter) == 0 + assert asset.balanceOf(splitter) == amount * 3 \ No newline at end of file From 2bf290d4de2f6731d9e126f22dcfab74fd05d380 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 8 Mar 2024 00:12:00 -0700 Subject: [PATCH 09/11] chore: deploy splitter --- contracts/splitter/Splitter.vy | 4 +- scripts/deploy_splitter_factory.py | 52 +++++++ scripts/deployments.py | 2 + tests/splitter/test_splitter.py | 234 +++++++++++++++++++++++++++-- 4 files changed, 281 insertions(+), 11 deletions(-) create mode 100644 scripts/deploy_splitter_factory.py diff --git a/contracts/splitter/Splitter.vy b/contracts/splitter/Splitter.vy index 8e461aa..25b1afa 100644 --- a/contracts/splitter/Splitter.vy +++ b/contracts/splitter/Splitter.vy @@ -188,4 +188,6 @@ def setMaxLoss(new_max_loss: uint256): def setAuction(new_auction: address): assert msg.sender == self.manager, "!manager" - self.auction = new_auction \ No newline at end of file + self.auction = new_auction + + log UpdateAuction(new_auction) \ No newline at end of file diff --git a/scripts/deploy_splitter_factory.py b/scripts/deploy_splitter_factory.py new file mode 100644 index 0000000..7bba33a --- /dev/null +++ b/scripts/deploy_splitter_factory.py @@ -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() diff --git a/scripts/deployments.py b/scripts/deployments.py index 5fe9b03..92e36ec 100644 --- a/scripts/deployments.py +++ b/scripts/deployments.py @@ -27,3 +27,5 @@ def deploy_contract(init_code, salt, deployer): print("------------------") print(f"Deployed the contract to {address}") + + return address diff --git a/tests/splitter/test_splitter.py b/tests/splitter/test_splitter.py index a3aa1fd..138e4d7 100644 --- a/tests/splitter/test_splitter.py +++ b/tests/splitter/test_splitter.py @@ -13,32 +13,37 @@ def test_split_setup(splitter_factory, splitter, daddy, brain, management): assert splitter.maxLoss() == 1 assert splitter.auction() == ZERO_ADDRESS -def test_unwrap(splitter, daddy, vault, mock_tokenized, strategy, asset, user, amount): + +def test_unwrap( + splitter, daddy, vault, mock_tokenized, deploy_mock_tokenized, asset, user, amount +): assert splitter.manager() == daddy + second_strategy = deploy_mock_tokenized() + amount = amount // 3 asset.approve(vault, amount, sender=user) asset.approve(mock_tokenized, amount, sender=user) - asset.approv(strategy, amount, sender=user) + asset.approve(second_strategy, amount, sender=user) vault.deposit(amount, splitter, sender=user) mock_tokenized.deposit(amount, splitter, sender=user) - strategy.deposit(amount, splitter, sender=user) + second_strategy.deposit(amount, splitter, sender=user) assert vault.balanceOf(splitter) == amount assert mock_tokenized.balanceOf(splitter) == amount - assert strategy.balanceOf(splitter) == amount + assert second_strategy.balanceOf(splitter) == amount assert asset.balanceOf(splitter) == 0 with ape.reverts("!allowed"): - splitter.unwrapVault(strategy, sender=user) + splitter.unwrapVault(second_strategy, sender=user) - splitter.unwrapVault(strategy, sender=daddy) + splitter.unwrapVault(second_strategy, sender=daddy) - assert vault.balanceOf(splitter) == 0 + assert vault.balanceOf(splitter) == amount assert mock_tokenized.balanceOf(splitter) == amount - assert strategy.balanceOf(splitter) == amount + assert second_strategy.balanceOf(splitter) == 0 assert asset.balanceOf(splitter) == amount vaults = [vault, mock_tokenized] @@ -50,5 +55,214 @@ def test_unwrap(splitter, daddy, vault, mock_tokenized, strategy, asset, user, a assert vault.balanceOf(splitter) == 0 assert mock_tokenized.balanceOf(splitter) == 0 - assert strategy.balanceOf(splitter) == 0 - assert asset.balanceOf(splitter) == amount * 3 \ No newline at end of file + assert second_strategy.balanceOf(splitter) == 0 + assert asset.balanceOf(splitter) == amount * 3 + + +def test_distribute( + splitter, + daddy, + vault, + mock_tokenized, + deploy_mock_tokenized, + asset, + user, + management, + brain, + amount, +): + assert splitter.manager() == daddy + recipeint = management + splitee = brain + split = 5_000 + + second_strategy = deploy_mock_tokenized() + + amount = amount // 4 + + asset.approve(vault, amount, sender=user) + asset.approve(mock_tokenized, amount, sender=user) + asset.approve(second_strategy, amount, sender=user) + + vault.deposit(amount, splitter, sender=user) + mock_tokenized.deposit(amount, splitter, sender=user) + second_strategy.deposit(amount, splitter, sender=user) + + assert vault.balanceOf(splitter) == amount + assert vault.balanceOf(recipeint) == 0 + assert vault.balanceOf(splitee) == 0 + + assert mock_tokenized.balanceOf(splitter) == amount + assert mock_tokenized.balanceOf(recipeint) == 0 + assert mock_tokenized.balanceOf(splitee) == 0 + + assert second_strategy.balanceOf(splitter) == amount + assert second_strategy.balanceOf(recipeint) == 0 + assert second_strategy.balanceOf(splitee) == 0 + + with ape.reverts("!allowed"): + splitter.distributeToken(second_strategy, sender=user) + + splitter.distributeToken(second_strategy, sender=daddy) + + assert second_strategy.balanceOf(splitter) == 0 + assert second_strategy.balanceOf(recipeint) == amount / 2 + assert second_strategy.balanceOf(splitee) == amount / 2 + + vaults = [vault, mock_tokenized] + + with ape.reverts("!allowed"): + splitter.distributeTokens(vaults, sender=user) + + splitter.distributeTokens(vaults, sender=daddy) + + assert vault.balanceOf(splitter) == 0 + assert vault.balanceOf(recipeint) == amount / 2 + assert vault.balanceOf(splitee) == amount / 2 + + assert mock_tokenized.balanceOf(splitter) == 0 + assert mock_tokenized.balanceOf(recipeint) == amount / 2 + assert mock_tokenized.balanceOf(splitee) == amount / 2 + + +def test_auction( + splitter, + daddy, + vault, + mock_tokenized, + deploy_mock_tokenized, + asset, + user, + management, + brain, + amount, +): + assert splitter.manager() == daddy + auction = user + + second_strategy = deploy_mock_tokenized() + + amount = amount // 4 + + asset.approve(vault, amount, sender=user) + asset.approve(mock_tokenized, amount, sender=user) + asset.approve(second_strategy, amount, sender=user) + + vault.deposit(amount, splitter, sender=user) + mock_tokenized.deposit(amount, splitter, sender=user) + second_strategy.deposit(amount, splitter, sender=user) + + assert vault.balanceOf(splitter) == amount + assert vault.balanceOf(auction) == 0 + + assert mock_tokenized.balanceOf(splitter) == amount + assert mock_tokenized.balanceOf(auction) == 0 + + assert second_strategy.balanceOf(splitter) == amount + assert second_strategy.balanceOf(auction) == 0 + + with ape.reverts("!allowed"): + splitter.fundAuction(second_strategy, sender=user) + + with ape.reverts(): + splitter.fundAuction(second_strategy, sender=daddy) + + splitter.setAuction(auction, sender=daddy) + + splitter.fundAuction(second_strategy, sender=daddy) + + assert second_strategy.balanceOf(splitter) == 0 + assert second_strategy.balanceOf(auction) == amount + + vaults = [vault, mock_tokenized] + + with ape.reverts("!allowed"): + splitter.fundAuctions(vaults, sender=user) + + splitter.fundAuctions(vaults, sender=daddy) + + assert vault.balanceOf(splitter) == 0 + assert vault.balanceOf(auction) == amount + + assert mock_tokenized.balanceOf(splitter) == 0 + assert mock_tokenized.balanceOf(auction) == amount + + +def test_setters(splitter, daddy, user, brain, management): + recipeint = management + splitee = brain + split = 5_000 + max_loss = 1 + + new_recipient = user + + assert splitter.managerRecipient() == recipeint + + with ape.reverts("!manager"): + splitter.setMangerRecipient(new_recipient, sender=brain) + + assert splitter.managerRecipient() == recipeint + + tx = splitter.setMangerRecipient(new_recipient, sender=daddy) + + assert splitter.managerRecipient() == new_recipient + assert ( + list(tx.decode_logs(splitter.UpdateManagerRecipient))[0].newManagerRecipient + == new_recipient + ) + + new_splitee = user + + assert splitter.splitee() == splitee + + with ape.reverts("!splitee"): + splitter.setSplitee(new_splitee, sender=daddy) + + assert splitter.splitee() == splitee + + tx = splitter.setSplitee(new_splitee, sender=brain) + + assert splitter.splitee() == new_splitee + assert list(tx.decode_logs(splitter.UpdateSplitee))[0].newSplitee == new_splitee + + new_split = 123 + + assert splitter.split() == split + + with ape.reverts("!manager"): + splitter.setSplit(new_split, sender=brain) + + assert splitter.split() == split + + tx = splitter.setSplit(new_split, sender=daddy) + + assert splitter.split() == new_split + assert list(tx.decode_logs(splitter.UpdateSplit))[0].newSplit == new_split + + new_max_loss = 123 + + assert splitter.maxLoss() == max_loss + + with ape.reverts("!manager"): + splitter.setMaxLoss(new_max_loss, sender=brain) + + assert splitter.maxLoss() == max_loss + + tx = splitter.setMaxLoss(new_split, sender=daddy) + + assert splitter.maxLoss() == new_max_loss + assert list(tx.decode_logs(splitter.UpdateMaxLoss))[0].newMaxLoss == new_max_loss + + new_auction = user + + assert splitter.auction() == ZERO_ADDRESS + + with ape.reverts("!manager"): + splitter.setAuction(new_auction, sender=brain) + + assert splitter.auction() == ZERO_ADDRESS + + tx = splitter.setAuction(new_auction, sender=daddy) + + assert splitter.auction() == new_auction + assert list(tx.decode_logs(splitter.UpdateAuction))[0].newAuction == new_auction From a2b804e91370a5d09d27f3965192da1a443cc4f3 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 8 Mar 2024 00:49:33 -0700 Subject: [PATCH 10/11] chore: role manager script --- scripts/deploy_role_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/deploy_role_manager.py b/scripts/deploy_role_manager.py index cea034a..e1cab53 100644 --- a/scripts/deploy_role_manager.py +++ b/scripts/deploy_role_manager.py @@ -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( From 0b07fc5f4732f966a3fd217af554ffc5835f1d20 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Fri, 8 Mar 2024 09:19:44 -0700 Subject: [PATCH 11/11] test: import reverts --- tests/splitter/test_splitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/splitter/test_splitter.py b/tests/splitter/test_splitter.py index 138e4d7..f77a6d1 100644 --- a/tests/splitter/test_splitter.py +++ b/tests/splitter/test_splitter.py @@ -1,5 +1,5 @@ import ape -from ape import chain +from ape import chain, reverts from utils.constants import ZERO_ADDRESS, MAX_INT