-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathSplitFeesModule.sol
More file actions
214 lines (167 loc) · 7.99 KB
/
SplitFeesModule.sol
File metadata and controls
214 lines (167 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
pragma solidity ^0.8.20;
import {Split} from "../libraries/Split.sol";
import {SplitFeesCore} from "../core/SplitFeesCore.sol";
import {IERC20} from "../interface/IERC20.sol";
import {ISplitWallet} from "../interface/ISplitWallet.sol";
import {AfterWithdrawCallback} from "../callback/AfterWithdrawCallback.sol";
import {BeforeDistributeCallback} from "../callback/BeforeDistributeCallback.sol";
import {LibClone} from "@solady/utils/LibClone.sol";
import {Module} from "../Module.sol";
library SplitFeesStorage {
/// @custom:storage-location erc7201:split.fees
bytes32 public constant SPLIT_FEES_STORAGE_POSITION =
keccak256(abi.encode(uint256(keccak256("split.fees")) - 1)) & ~bytes32(uint256(0xff));
struct Data {
mapping(address => Split) splits;
}
function data() internal pure returns (Data storage data_) {
bytes32 position = SPLIT_FEES_STORAGE_POSITION;
assembly {
data_.slot := position
}
}
}
contract SplitFeesModule is Module, BeforeDistributeCallback, AfterWithdrawCallback {
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice address of the native token, inline with ERC 7528.
address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event SplitCreated(
address indexed splitWallet,
address[] recipients,
uint256[] allocations,
address controller,
address referenceContract
);
event SplitsUpdated(address indexed splitWallet, address[] recipients, uint256[] allocations, address controller);
event ControllerUpdated(address indexed splitWallet, address controller);
event SplitsDistributed(address indexed splitWallet, address token, uint256 amount);
event SplitsWithdrawn(address indexed recipient, address token, uint256 amount);
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error SplitFeesTooFewRecipients();
error SplitFeesLengthMismatch();
error SplitFeesNotController();
error SplitFeesAmountMismatch();
error SplitFeesNothingToWithdraw();
error SplitFeesWithdrawFailed();
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyController(address _splitWallet) {
if (_splitFeesStorage().splits[_splitWallet].controller != msg.sender) {
revert SplitFeesNotController();
}
_;
}
modifier validateSplits(address[] memory _recipients, uint256[] memory _allocations, address _controller) {
if (_recipients.length < 2) {
revert SplitFeesTooFewRecipients();
}
if (_recipients.length != _allocations.length) {
revert SplitFeesLengthMismatch();
}
_;
}
/*//////////////////////////////////////////////////////////////
MODULE CONFIG
//////////////////////////////////////////////////////////////*/
function getModuleConfig() external pure virtual override returns (ModuleConfig memory) {
ModuleConfig memory config;
config.callbackFunctions = new CallbackFunction[](2);
config.callbackFunctions[0] = CallbackFunction(this.beforeDistribute.selector);
config.callbackFunctions[1] = CallbackFunction(this.afterWithdraw.selector);
config.fallbackFunctions = new FallbackFunction[](3);
config.fallbackFunctions[0] = FallbackFunction({selector: this.createSplit.selector, permissionBits: 0});
config.fallbackFunctions[1] = FallbackFunction({selector: this.updateSplit.selector, permissionBits: 0});
config.fallbackFunctions[2] = FallbackFunction({selector: this.getSplit.selector, permissionBits: 0});
return config;
}
/*//////////////////////////////////////////////////////////////
CALLBACK FUNCTIONS
//////////////////////////////////////////////////////////////*/
function beforeDistribute(address _splitWallet, address _token) external override returns (uint256, Split memory) {
Split memory _split = _splitFeesStorage().splits[_splitWallet];
uint256 amountToSplit;
if (_token == NATIVE_TOKEN_ADDRESS) {
amountToSplit = _splitWallet.balance;
ISplitWallet(_splitWallet).transferETH(amountToSplit);
} else {
amountToSplit = IERC20(_token).balanceOf(_splitWallet);
ISplitWallet(_splitWallet).transferERC20(_token, amountToSplit);
}
emit SplitsDistributed(_splitWallet, _token, amountToSplit);
return (amountToSplit, _split);
}
function afterWithdraw(uint256 amountToWithdraw, address account, address _token) external override {
if (amountToWithdraw == 0) {
revert SplitFeesNothingToWithdraw();
}
if (_token == NATIVE_TOKEN_ADDRESS) {
(bool success,) = payable(account).call{value: amountToWithdraw}("");
if (!success) {
revert SplitFeesWithdrawFailed();
}
} else {
IERC20(_token).transfer(account, amountToWithdraw);
}
emit SplitsWithdrawn(account, _token, amountToWithdraw);
}
/*//////////////////////////////////////////////////////////////
FALLBACK FUNCTIONS
//////////////////////////////////////////////////////////////*/
// Core contract calls this in constructor
function createSplit(
address[] memory _recipients,
uint256[] memory _allocations,
address _controller,
address _referenceContract
) external validateSplits(_recipients, _allocations, _controller) {
Split memory _split = _setSplits(_recipients, _allocations, _controller);
address splitWalletImplementation = SplitFeesCore(payable(address(this))).splitWalletImplementation();
address splitWallet = LibClone.clone(splitWalletImplementation);
_splitFeesStorage().splits[splitWallet] = _split;
emit SplitCreated(splitWallet, _recipients, _allocations, _controller, _referenceContract);
}
function updateSplit(
address _splitWallet,
address[] memory _recipients,
uint256[] memory _allocations,
address _controller
) external onlyController(_splitWallet) validateSplits(_recipients, _allocations, _controller) {
Split memory _split = _setSplits(_recipients, _allocations, _controller);
_splitFeesStorage().splits[_splitWallet] = _split;
emit SplitsUpdated(_splitWallet, _recipients, _allocations, _controller);
}
function getSplit(address _splitWallet) external view returns (Split memory) {
return _splitFeesStorage().splits[_splitWallet];
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function _setSplits(address[] memory _recipients, uint256[] memory _allocations, address _controller)
internal
pure
returns (Split memory)
{
Split memory _split;
uint256 _totalAllocation;
uint256 length = _recipients.length;
for (uint256 i = 0; i < length; i++) {
_totalAllocation += _allocations[i];
}
_split.recipients = _recipients;
_split.allocations = _allocations;
_split.totalAllocation = _totalAllocation;
_split.controller = _controller;
return _split;
}
function _splitFeesStorage() internal pure returns (SplitFeesStorage.Data storage) {
return SplitFeesStorage.data();
}
}