-
Notifications
You must be signed in to change notification settings - Fork 3
fee splitter #45
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
base: master
Are you sure you want to change the base?
fee splitter #45
Changes from all commits
8302ef7
04c7c01
321fcc2
39a128d
3b00832
5baab5c
bf5b28d
5519e89
3b5c569
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| pragma solidity ^0.8.18; | ||
|
|
||
| import "lyra-utils/decimals/SignedDecimalMath.sol"; | ||
|
|
||
| import {Ownable2Step} from "openzeppelin/access/Ownable2Step.sol"; | ||
| import {IManager} from "v2-core/src/interfaces/IManager.sol"; | ||
| import {ISubAccounts, IAsset} from "v2-core/src/interfaces/ISubAccounts.sol"; | ||
|
|
||
| /** | ||
| * @title FeeSplitter | ||
| * @notice FeeSplitter is a contract that splits the balance of a subaccount held by this contract based on a % split | ||
| * @author Lyra | ||
| */ | ||
| contract FeeSplitter is Ownable2Step { | ||
| using SignedDecimalMath for int; | ||
|
|
||
| ISubAccounts public immutable subAccounts; | ||
| IAsset public immutable cashAsset; | ||
|
|
||
| uint public subAcc; | ||
| uint public splitPercent; | ||
| uint public accountA; | ||
| uint public accountB; | ||
|
|
||
| constructor( | ||
| ISubAccounts _subAccounts, | ||
| IManager manager, | ||
| IAsset _cashAsset, | ||
| uint _splitPercent, | ||
| uint _accountA, | ||
| uint _accountB | ||
| ) { | ||
| subAccounts = _subAccounts; | ||
| subAcc = _subAccounts.createAccount(address(this), manager); | ||
| cashAsset = _cashAsset; | ||
|
|
||
| _setSplit(_splitPercent); | ||
| _setSubAccounts(_accountA, _accountB); | ||
| } | ||
|
|
||
| /////////// | ||
| // Admin // | ||
| /////////// | ||
|
|
||
| /// @notice Set the % split | ||
| function setSplit(uint _splitPercent) external onlyOwner { | ||
| _setSplit(_splitPercent); | ||
| } | ||
|
|
||
| function _setSplit(uint _splitPercent) internal { | ||
| if (_splitPercent > 1e18) { | ||
| revert FS_InvalidSplitPercentage(); | ||
| } | ||
| splitPercent = _splitPercent; | ||
|
|
||
| emit SplitPercentSet(_splitPercent); | ||
| } | ||
|
|
||
| /// @notice Set the subaccounts to split the balance between | ||
| function setSubAccounts(uint _accountA, uint _accountB) external onlyOwner { | ||
| _setSubAccounts(_accountA, _accountB); | ||
| } | ||
|
|
||
| function _setSubAccounts(uint _accountA, uint _accountB) internal { | ||
| if (_accountA == 0 || _accountB == 0) { | ||
| revert FS_InvalidSubAccount(); | ||
| } | ||
|
|
||
| accountA = _accountA; | ||
| accountB = _accountB; | ||
|
|
||
| emit SubAccountsSet(_accountA, _accountB); | ||
| } | ||
|
|
||
| /// @notice Recover a subaccount held by this contract, creating a new one in its place | ||
| function recoverSubAccount(address recipient) external onlyOwner { | ||
| uint oldSubAcc = subAcc; | ||
| subAccounts.transferFrom(address(this), recipient, oldSubAcc); | ||
| subAcc = subAccounts.createAccount(address(this), subAccounts.manager(oldSubAcc)); | ||
|
|
||
| emit SubAccountRecovered(oldSubAcc, recipient, subAcc); | ||
| } | ||
|
|
||
| ////////////// | ||
| // External // | ||
| ////////////// | ||
| /// @notice Work out the balance of the subaccount held by this contract, and split it based on the % split | ||
| function split() external { | ||
| int balance = subAccounts.getBalance(subAcc, cashAsset, 0); | ||
|
|
||
| if (balance <= 0) { | ||
| revert FS_NoBalanceToSplit(); | ||
| } | ||
|
|
||
| int splitAmountA = balance.multiplyDecimal(int(splitPercent)); | ||
| int splitAmountB = balance - splitAmountA; | ||
|
|
||
| ISubAccounts.AssetTransfer[] memory transfers = new ISubAccounts.AssetTransfer[](2); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add a check that
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or would run |
||
| transfers[0] = ISubAccounts.AssetTransfer({ | ||
| fromAcc: subAcc, | ||
| toAcc: accountA, | ||
| asset: cashAsset, | ||
| subId: 0, | ||
| amount: int(splitAmountA), | ||
| assetData: bytes32(0) | ||
| }); | ||
| transfers[1] = ISubAccounts.AssetTransfer({ | ||
| fromAcc: subAcc, | ||
| toAcc: accountB, | ||
| asset: cashAsset, | ||
| subId: 0, | ||
| amount: int(splitAmountB), | ||
| assetData: bytes32(0) | ||
| }); | ||
|
|
||
| subAccounts.submitTransfers(transfers, ""); | ||
|
|
||
| emit BalanceSplit(subAcc, accountA, accountB, splitAmountA, splitAmountB); | ||
| } | ||
|
|
||
| //////////// | ||
| // Errors // | ||
| //////////// | ||
| error FS_InvalidSplitPercentage(); | ||
| error FS_InvalidSubAccount(); | ||
| error FS_NoBalanceToSplit(); | ||
|
|
||
| //////////// | ||
| // Events // | ||
| //////////// | ||
| event SplitPercentSet(uint splitPercent); | ||
| event SubAccountsSet(uint accountA, uint accountB); | ||
| event SubAccountRecovered(uint oldSubAcc, address recipient, uint newSubAcc); | ||
| event BalanceSplit(uint subAcc, uint accountA, uint accountB, int splitAmountA, int splitAmountB); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| pragma solidity ^0.8.18; | ||
|
|
||
| import {IntegrationTestBase} from "v2-core/test/integration-tests/shared/IntegrationTestBase.t.sol"; | ||
| import {IManager} from "v2-core/src/interfaces/IManager.sol"; | ||
| import {IAsset} from "v2-core/src/interfaces/IAsset.sol"; | ||
| import {ISubAccounts} from "v2-core/src/interfaces/ISubAccounts.sol"; | ||
|
|
||
| import "forge-std/console2.sol"; | ||
| import "../../src/periphery/FeeSplitter.sol"; | ||
|
|
||
| contract FeeSplitterTest is IntegrationTestBase { | ||
| FeeSplitter public feeSplitter; | ||
| IAsset public asset; | ||
| uint public accountA; | ||
| uint public accountB; | ||
|
|
||
| function setUp() public { | ||
| _setupIntegrationTestComplete(); | ||
|
|
||
| accountA = subAccounts.createAccount(address(this), srm); | ||
| accountB = subAccounts.createAccount(address(this), markets["weth"].pmrm); | ||
|
|
||
| feeSplitter = new FeeSplitter(subAccounts, srm, cash, 0.5e18, accountA, accountB); | ||
| uint amount = 100e6; | ||
| usdc.mint(address(this), amount); | ||
| usdc.approve(address(cash), amount); | ||
| cash.deposit(feeSplitter.subAcc(), amount); | ||
| } | ||
|
|
||
| function test_constructor() public { | ||
| assertEq(address(feeSplitter.subAccounts()), address(subAccounts), "Incorrect subAccounts"); | ||
| assertEq(address(feeSplitter.cashAsset()), address(cash), "Incorrect cashAsset"); | ||
| assertFalse(feeSplitter.subAcc() == 0, "Incorrect subAcc"); | ||
| assertEq(feeSplitter.splitPercent(), 0.5e18, "Incorrect splitPercent"); | ||
| assertEq(feeSplitter.accountA(), accountA, "Incorrect accountA"); | ||
| assertEq(feeSplitter.accountB(), accountB, "Incorrect accountB"); | ||
| } | ||
|
|
||
| function test_setSplit() public { | ||
| feeSplitter.setSplit(0.6e18); | ||
| assertEq(feeSplitter.splitPercent(), 0.6e18, "Incorrect splitPercent"); | ||
|
|
||
| vm.expectRevert(FeeSplitter.FS_InvalidSplitPercentage.selector); | ||
| feeSplitter.setSplit(1.1e18); | ||
| } | ||
|
|
||
| function test_split() public { | ||
| // before split | ||
| assertEq( | ||
| subAccounts.getBalance(feeSplitter.subAcc(), cash, 0), int(100e18), "Incorrect initial balance in FeeSplitter" | ||
| ); | ||
| assertEq(subAccounts.getBalance(accountA, cash, 0), 0, "Account A should initially have 0 balance"); | ||
| assertEq(subAccounts.getBalance(accountB, cash, 0), 0, "Account B should initially have 0 balance"); | ||
|
|
||
| feeSplitter.split(); | ||
|
|
||
| // after split | ||
| assertEq( | ||
| subAccounts.getBalance(accountA, cash, 0), int(50e18), "Account A should have half of the funds after split" | ||
| ); | ||
| assertEq( | ||
| subAccounts.getBalance(accountB, cash, 0), int(50e18), "Account B should have half of the funds after split" | ||
| ); | ||
|
|
||
| vm.expectRevert(FeeSplitter.FS_NoBalanceToSplit.selector); | ||
| feeSplitter.split(); | ||
| } | ||
|
|
||
| function test_recover() public { | ||
| uint oldSubAcc = feeSplitter.subAcc(); | ||
| feeSplitter.recoverSubAccount(address(this)); | ||
| uint newSubAcc = feeSplitter.subAcc(); | ||
| assertEq( | ||
| subAccounts.ownerOf(newSubAcc), address(feeSplitter), "New subAcc should be owned by fee splitter contract" | ||
| ); | ||
| assertEq(subAccounts.ownerOf(oldSubAcc), address(this), "Old subAcc should be owned by this contract"); | ||
| assertFalse(oldSubAcc == newSubAcc); | ||
| } | ||
|
|
||
| function test_split100percent() public { | ||
| feeSplitter.setSplit(1e18); | ||
| feeSplitter.split(); | ||
| assertEq( | ||
| subAccounts.getBalance(accountA, cash, 0), int(100e18), "Account A should have all of the funds after split" | ||
| ); | ||
| assertEq(subAccounts.getBalance(accountB, cash, 0), 0, "Account B should have 0 balance after split"); | ||
| } | ||
|
|
||
| function test_split0percent() public { | ||
| feeSplitter.setSplit(0); | ||
| feeSplitter.split(); | ||
| assertEq(subAccounts.getBalance(accountA, cash, 0), 0, "Account A should have 0 balance after split"); | ||
| assertEq( | ||
| subAccounts.getBalance(accountB, cash, 0), int(100e18), "Account B should have all of the funds after split" | ||
| ); | ||
| } | ||
|
|
||
| function test_setSubAccounts() public { | ||
| feeSplitter.setSubAccounts(accountB, accountA); | ||
| assertEq(feeSplitter.accountA(), accountB, "Incorrect accountA"); | ||
| assertEq(feeSplitter.accountB(), accountA, "Incorrect accountB"); | ||
|
|
||
| vm.expectRevert(FeeSplitter.FS_InvalidSubAccount.selector); | ||
| feeSplitter.setSubAccounts(0, accountA); | ||
|
|
||
| vm.expectRevert(FeeSplitter.FS_InvalidSubAccount.selector); | ||
| feeSplitter.setSubAccounts(accountB, 0); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think cash and managers allow amount=0 transfers, but might be worth double checking.
E.g. if
_splitPercentis 0 or 1, you'd need to make 0 amount transfers insplit()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pushed test for this 👌