-
Notifications
You must be signed in to change notification settings - Fork 30
/
GasliteSplitter.sol
383 lines (329 loc) · 16.5 KB
/
GasliteSplitter.sol
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
pragma solidity 0.8.20;
// forgefmt: disable-start
/**
* bbbbbbbb dddddddd
* b::::::b d::::::d
* b::::::b d::::::d
* b::::::b d::::::d
* b:::::b d:::::d
* ggggggggg ggggg aaaaaaaaaaaaa ssssssssss b:::::bbbbbbbbb aaaaaaaaaaaaa ddddddddd:::::d
* g:::::::::ggg::::g a::::::::::::a ss::::::::::s b::::::::::::::bb a::::::::::::a dd::::::::::::::d
* g:::::::::::::::::g aaaaaaaaa:::::ass:::::::::::::s b::::::::::::::::b aaaaaaaaa:::::a d::::::::::::::::d
* g::::::ggggg::::::gg a::::as::::::ssss:::::s b:::::bbbbb:::::::b a::::ad:::::::ddddd:::::d
* g:::::g g:::::g aaaaaaa:::::a s:::::s ssssss b:::::b b::::::b aaaaaaa:::::ad::::::d d:::::d
* g:::::g g:::::g aa::::::::::::a s::::::s b:::::b b:::::b aa::::::::::::ad:::::d d:::::d
* g:::::g g:::::g a::::aaaa::::::a s::::::s b:::::b b:::::b a::::aaaa::::::ad:::::d d:::::d
* g::::::g g:::::ga::::a a:::::assssss s:::::s b:::::b b:::::ba::::a a:::::ad:::::d d:::::d
* g:::::::ggggg:::::ga::::a a:::::as:::::ssss::::::s b:::::bbbbbb::::::ba::::a a:::::ad::::::ddddd::::::dd
* g::::::::::::::::ga:::::aaaa::::::as::::::::::::::s b::::::::::::::::b a:::::aaaa::::::a d:::::::::::::::::d
* gg::::::::::::::g a::::::::::aa:::as:::::::::::ss b:::::::::::::::b a::::::::::aa:::a d:::::::::ddd::::d
* gggggggg::::::g aaaaaaaaaa aaaa sssssssssss bbbbbbbbbbbbbbbb aaaaaaaaaa aaaa ddddddddd ddddd
* g:::::g
* gggggg g:::::g
* g:::::gg gg:::::g
* g::::::ggg:::::::g
* gg:::::::::::::g
* ggg::::::ggg
* gggggg
*/
// forgefmt: disable-end
/// @title GasliteSplitter
/// @notice Turbo gas optimized payment splitter
/// @author Harrison (@PopPunkOnChain)
/// @author Thomas (@0xjustadev)
/// @author Gaslite (@GasliteGG)
contract GasliteSplitter {
/**
* packed data for split receivers
* address: bytes 0-19
* share: bytes 20-31
* Example:
* [5, 5, 5, 5] -> Each address gets 25% (20 shares total)
* [10, 20, 30, 40] -> Address 1 gets 10%, Address 2 gets 20%,
* Address 3 gets 30%, Address 4 gets 40%
* (100 shares total)
*/
bytes32[] private packedSplits;
// event emitted when a split is released
bytes32 private constant SPLIT_RELEASED_EVENT_SIGNATURE =
0xa81a1a3f8e5470cb88006c7539ae66f8750a18c49bf0d312ef679e24bac0f014;
// event emitted when ether is received
bytes32 private constant PAYMENT_RECEIVED_EVENT_SIGNATURE =
0x6ef95f06320e7a25a04a175ca677b7052bdd97131872c2192525a629f51be770;
// hash of packed split slot
bytes32 private immutable HASH_OF_PACKED_SPLIT_SLOT;
// the total number of shares (calculated in constructor)
uint256 public immutable totalShares;
// flag to optionally give 0.1% to caller of release()
bool public immutable releaseRoyalty;
// event emitted when a payment is received (OpenZeppelin did this so I guess I have to do it too)
event PaymentReceived(address from, uint256 amount);
// event emitted when tokens are split
event SplitReleased(address[] recipients, uint256[] amounts);
// error when the balance is zero
error BalanceZero();
/// @notice Split payments to a list of addresses
/// @param _recipients The addresses to split to
/// @param _shares The shares for each address
/// @param _releaseRoyalty Optional flag to give 0.1% to caller of release()
constructor(address[] memory _recipients, uint256[] memory _shares, bool _releaseRoyalty) {
// solidity accesible cache of hashOfPackedSplitSlot
bytes32 hashOfPackedSplitSlot;
// running total of sum of _shares array
uint256 accumulatedShares;
assembly {
// cache size of _recipients
let size := mload(_recipients)
// revert if _recipients is empty
// or if _recipients and _shares are different sizes
if or(iszero(size), iszero(eq(size, mload(_shares)))) { revert(0, 0) }
// loop iterator
let sharesOffset := add(_shares, 0x20)
let recipientsOffset := sub(_shares, _recipients)
// end of array
let end := add(sharesOffset, mul(size, 0x20))
// store array size to packedSplits slot
sstore(packedSplits.slot, size)
// store hash of packedSlits slot to get first storage slot for array data
mstore(0x00, packedSplits.slot)
hashOfPackedSplitSlot := keccak256(0x00, 0x20)
let splitsSlot := hashOfPackedSplitSlot
for {} 1 {} {
// load share and recipient
let share := mload(sharesOffset)
let addr := mload(sub(sharesOffset, recipientsOffset))
// add each share to accumulatedShares
accumulatedShares := add(accumulatedShares, share)
// revert if share is zero or share > 2^96-1
if or(iszero(share), gt(share, 0xFFFFFFFFFFFFFFFFFFFFFFFF)) { revert(0, 0) }
// store packed data
sstore(splitsSlot, or(share, shl(96, addr)))
// increment iterator
sharesOffset := add(sharesOffset, 0x20)
// break at end of array
if eq(end, sharesOffset) { break }
// increment split slot after end of array check
splitsSlot := add(splitsSlot, 0x01)
}
}
// hash of packed split slot, release royalty and totalShares are set outside of assembly block
// because they're immutable to save gas on SLOAD
releaseRoyalty = _releaseRoyalty;
totalShares = accumulatedShares;
HASH_OF_PACKED_SPLIT_SLOT = hashOfPackedSplitSlot;
}
/// @notice Release all eth (address(this).balance) to the recipients
function release() external {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
// cache releaseRoyalty unto the stack
bool memReleaseRoyalty = releaseRoyalty;
// cache totalShares unto the stack
uint256 total = totalShares;
// initiate the arrays in memory
assembly {
// cache balance of this contract
let bal := selfbalance()
// revert early if address(this).balance is 0
if iszero(bal) {
mstore(0x00, hex"836fd8a7")
revert(0x00, 0x04)
}
let size := sload(packedSplits.slot)
let length := add(0x20, mul(0x20, size))
// canonical memAddresses array pointer, this returns the offset of the length of memAddresses
let memAddresses := 0x40
// canonical amounts array pointer, this returns the offset of the length of amounts
let amounts := add(memAddresses, length)
mstore(memAddresses, size)
mstore(amounts, size)
// abi encoding value for addresses position
mstore(sub(memAddresses, 0x40), 0x40)
// abi encoding value for amounts position
mstore(sub(memAddresses, 0x20), add(0x40, length))
// if releaseRoyalty == true
if memReleaseRoyalty {
// calculate 0.1% of balance as royalty
let royalty := div(bal, 1000)
// subtract royalty from balance
bal := sub(bal, royalty)
// transfer royalty to caller
if iszero(call(gas(), caller(), royalty, 0, 0, 0, 0)) { revert(0, 0) }
}
// get first packed slot, memory pointer, offsets, and end
let splitSlot := hashOfPackedSplitSlot
let amountsOffset := add(amounts, 0x20)
let addrOffset := sub(amounts, memAddresses)
let end := add(amountsOffset, mul(mload(amounts), 0x20))
for {} 1 {} {
// load packed split data
let split := sload(splitSlot)
// calculate amount
let amount := div(mul(bal, and(split, 0xFFFFFFFFFFFFFFFFFFFFFFFF)), total)
// retrieve address from packed data
let addr := shr(96, split)
// Store the amount and address at the correct offsets
mstore(amountsOffset, amount)
mstore(sub(amountsOffset, addrOffset), addr)
// send ETH, revert if call fails
if iszero(call(gas(), addr, amount, 0, 0, 0, 0)) { revert(0, 0) }
// increment pointer
amountsOffset := add(amountsOffset, 0x20)
// break at end of array
if iszero(lt(amountsOffset, end)) { break }
// increment splitSlot after end of array check
splitSlot := add(splitSlot, 0x01)
}
// emit a bulk event of addresses and amounts
log1(sub(memAddresses, 0x40), add(0x40, mul(addrOffset, 0x02)), SPLIT_RELEASED_EVENT_SIGNATURE)
stop()
}
}
/// @notice Release all of given token (IERC20(_token).balanceOf(address(this))) to the recipients
/// @param _token The address of the token to release
function release(address _token) external {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
// cache releaseRoyalty into stack
bool memReleaseRoyalty = releaseRoyalty;
// cache totalShares unto the stack
uint256 total = totalShares;
// initiate the arrays in memory
assembly {
// cache balance of _token in this contract
mstore(0x00, hex"70a08231")
mstore(0x04, address())
// if `_token` has no code deployed to it, returndatacopy would revert since returndata[(offset + length)] is greater than returndata.length
if iszero(staticcall(gas(), _token, 0x00, 0x24, 0x00, 0x00)) { revert(0x00, 0x00) }
returndatacopy(0x00, 0x00, 0x20)
let bal := mload(0x00)
// revert early if address(this).balance is 0
if iszero(bal) {
mstore(0x00, hex"836fd8a7")
revert(0x00, 0x04)
}
let size := sload(packedSplits.slot)
let length := add(0x20, mul(0x20, size))
let memAddresses := add(0x40, 0x60)
let amounts := add(memAddresses, length)
mstore(memAddresses, size)
mstore(amounts, size)
// abi encoding value for addresses position
mstore(sub(memAddresses, 0x40), 0x40)
// abi encoding value for amounts position
mstore(sub(memAddresses, 0x20), add(0x40, length))
// if releaseRoyalty == true
if memReleaseRoyalty {
// calculate 0.1% of balance as royalty
let royalty := div(bal, 1000)
// subtract royalty from balance
bal := sub(bal, royalty)
// transfer(address to, uint256 value)
mstore(0x00, hex"a9059cbb")
// to address
mstore(0x04, caller())
// value
mstore(0x24, royalty)
// transfer royalty to caller
if iszero(call(gas(), _token, 0, 0x00, 0x44, 0, 0)) { revert(0, 0) }
}
// get first packed slot, memory pointer, offsets, and end
let splitSlot := hashOfPackedSplitSlot
let amountsOffset := add(amounts, 0x20)
let addrOffset := sub(amounts, memAddresses)
let end := add(amountsOffset, mul(mload(amounts), 0x20))
// transfer(address to, uint256 value)
mstore(0x00, hex"a9059cbb")
for {} 1 {} {
// load packed split data
let split := sload(splitSlot)
// calculate amount
let amount := div(mul(bal, and(split, 0xFFFFFFFFFFFFFFFFFFFFFFFF)), total)
// retrieve address from packed data
let addr := shr(96, split)
// Store the amount and address at the correct offsets
mstore(amountsOffset, amount)
mstore(sub(amountsOffset, addrOffset), addr)
// to address
mstore(0x04, addr)
// value
mstore(0x24, amount)
// transfer the tokens, revert if call fails
if iszero(call(gas(), _token, 0, 0x00, 0x44, 0, 0)) { revert(0, 0) }
// increment pointer
amountsOffset := add(amountsOffset, 0x20)
// break at end of array
if iszero(lt(amountsOffset, end)) { break }
// increment splitSlot after end of array check
splitSlot := add(splitSlot, 0x01)
}
mstore(0x24, 0x00)
// emit a bulk event of addresses and amounts
log1(sub(memAddresses, 0x40), add(0x40, mul(addrOffset, 0x02)), SPLIT_RELEASED_EVENT_SIGNATURE)
stop()
}
}
/// @notice Retrieve the address for a split recipient at given `index`
/// @param index The index of the split recipient
function recipients(uint256 index) external view returns (address recipient) {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
assembly {
if iszero(lt(index, sload(packedSplits.slot))) { revert(0, 0) }
recipient := shr(96, sload(add(index, hashOfPackedSplitSlot)))
}
}
/// @notice Retrieve an array of split recipients
function recipients() external view returns (address[] memory _recipients) {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
_recipients = new address[](packedSplits.length);
assembly {
let splitSlot := hashOfPackedSplitSlot
let ptr := add(0x20, _recipients)
let end := add(0x20, mul(0x20, mload(_recipients)))
for {} 1 {} {
mstore(ptr, shr(96, sload(splitSlot)))
ptr := add(0x20, ptr)
if iszero(lt(ptr, end)) { break }
splitSlot := add(0x01, splitSlot)
}
}
}
/// @notice Retrieve the shares for a split recipient at given `index`
/// @param index The index of the split shares
function shares(uint256 index) external view returns (uint256 share) {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
assembly {
if iszero(lt(index, sload(packedSplits.slot))) { revert(0, 0) }
share := and(0xFFFFFFFFFFFFFFFFFFFFFFFF, sload(add(index, hashOfPackedSplitSlot)))
}
}
/// @notice Retrieve an array of split recipients shares
function shares() external view returns (uint256[] memory _shares) {
// cache to the stack as immutable vars can't be accessed in assembly blocks
bytes32 hashOfPackedSplitSlot = HASH_OF_PACKED_SPLIT_SLOT;
_shares = new uint256[](packedSplits.length);
assembly {
let splitSlot := hashOfPackedSplitSlot
let ptr := add(0x20, _shares)
let end := add(0x20, mul(0x20, mload(_shares)))
for {} 1 {} {
mstore(ptr, and(0xFFFFFFFFFFFFFFFFFFFFFFFF, sload(splitSlot)))
ptr := add(0x20, ptr)
if iszero(lt(ptr, end)) { break }
splitSlot := add(0x01, splitSlot)
}
}
}
// receive function to receive ETH
receive() external payable {
// emit event when contract receives ETH
assembly {
mstore(0x00, caller())
mstore(0x20, callvalue())
log1(0x00, 0x40, PAYMENT_RECEIVED_EVENT_SIGNATURE)
}
}
}