-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathERC20Splitter.sol
More file actions
159 lines (127 loc) · 5.87 KB
/
ERC20Splitter.sol
File metadata and controls
159 lines (127 loc) · 5.87 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
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Roles} from "@protocol/core/Roles.sol";
import {CoreRef} from "@protocol/refs/CoreRef.sol";
import {Constants} from "@protocol/Constants.sol";
/// @title contract for splitting ERC20 tokens into different deposits
contract ERC20Splitter is CoreRef {
using SafeERC20 for IERC20;
/// @notice list of Deposits to split to
struct Allocation {
/// @notice address of the deposit
address deposit;
/// @notice ratio for splitting between allocations
uint16 ratio;
}
/// @notice list of Deposits to split to
Allocation[] public allocations;
/// @notice token to split
IERC20 public immutable token;
/// @notice emitted when the allocation is updated
event AllocationUpdate(address[] oldDeposits, uint256[] oldRatios, address[] newDeposits, uint256[] newRatios);
/// @notice emitted when funds are allocated to deposits
event Allocate(address indexed caller, uint256 amount);
/// @notice ERC20Splitter constructor
/// @param _core address of the core contract
/// @param _token address of the token to split
/// @param _deposits list of deposits and ratios to split to, optional, this can be provided later by admin
constructor(address _core, address _token, Allocation[] memory _deposits) CoreRef(_core) {
require(_token != address(0), "ERC20Splitter: token cannot be address(0)");
token = IERC20(_token);
/// do not set up deposits if non provided
if (_deposits.length > 0) {
_setAllocation(_deposits);
}
}
/// ------- View Only Functions -------
/// @notice get the number of allocations
function getNumberOfAllocations() external view returns (uint256) {
return allocations.length;
}
/// @notice get the allocation at a given index
/// @param index the index of the allocation
/// @return the allocation at the index
function getAllocationAt(uint256 index) external view returns (Allocation memory) {
return allocations[index];
}
/// @notice get all the allocations
/// @return the allocations
function getAllocations() external view returns (Allocation[] memory) {
return allocations;
}
/// @notice make sure an allocation has ratios that total ALLOCATION_GRANULARITY
/// @param _deposits new list of deposits to send to
function checkAllocation(Allocation[] memory _deposits) public pure {
uint256 total;
unchecked {
for (uint256 i; i < _deposits.length; i++) {
total = total + _deposits[i].ratio;
}
}
require(total == Constants.BASIS_POINTS_GRANULARITY, "ERC20Splitter: ratios not 100%");
}
/// ------- Public Function -------
/// @notice allocate all funds in the splitter to the deposits
/// @dev callable when not paused
function allocate() external whenNotPaused {
uint256 total = token.balanceOf(address(this));
uint256 granularity = Constants.BASIS_POINTS_GRANULARITY;
for (uint256 i; i < allocations.length; i++) {
uint256 amount = (total * allocations[i].ratio) / granularity;
token.safeTransfer(allocations[i].deposit, amount);
}
emit Allocate(msg.sender, total);
}
/// @notice allocate all funds in the splitter to the deposits
/// @dev callable when not paused
/// @param tokenToAllocate token to be allocated by splitter based on defined ratios
function allocate(address tokenToAllocate) external whenNotPaused {
uint256 total = IERC20(tokenToAllocate).balanceOf(address(this));
uint256 granularity = Constants.BASIS_POINTS_GRANULARITY;
for (uint256 i; i < allocations.length; i++) {
uint256 amount = (total * allocations[i].ratio) / granularity;
IERC20(tokenToAllocate).safeTransfer(allocations[i].deposit, amount);
}
emit Allocate(msg.sender, total);
}
/// ------- Admin Only Functions -------
/// @notice sets the allocation of each deposit
/// callable only by the admin role
/// @param _allocations list of deposits to send to
function setAllocation(Allocation[] memory _allocations) external onlyRole(Roles.ADMIN) {
_setAllocation(_allocations);
}
/// ------- Internal Helper Function -------
/// @notice sets a new allocation for the splitter
/// @param _allocations new list of allocations
function _setAllocation(Allocation[] memory _allocations) internal {
/// make sure all deposits are not address(0)
unchecked {
for (uint256 i; i < allocations.length; i++) {
require(_allocations[i].deposit != address(0), "ERC20Splitter: deposit cannot be address(0)");
}
}
checkAllocation(_allocations);
uint256[] memory _oldRatios = new uint256[](allocations.length);
address[] memory _oldDeposits = new address[](allocations.length);
unchecked {
for (uint256 i; i < allocations.length; i++) {
_oldRatios[i] = allocations[i].ratio;
_oldDeposits[i] = allocations[i].deposit;
}
}
/// drop all the allocations
delete allocations;
uint256[] memory _newRatios = new uint256[](_allocations.length);
address[] memory _newDeposits = new address[](_allocations.length);
/// improbable to ever overflow because deposit length would need to be greater than 2^256-1
unchecked {
for (uint256 i; i < _allocations.length; i++) {
allocations.push(_allocations[i]);
}
}
emit AllocationUpdate(_oldDeposits, _oldRatios, _newDeposits, _newRatios);
}
}