-
Notifications
You must be signed in to change notification settings - Fork 3
/
Dividends.sol
329 lines (292 loc) · 12.5 KB
/
Dividends.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
/**
* @title Dividends
* @author Team 3301 <team3301@sygnum.com>
* @notice Dividends payout.
*/
pragma solidity 0.5.12;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "@sygnum/solidity-base-contracts/contracts/helpers/Pausable.sol";
import "../token/SygnumToken.sol";
// solhint-disable max-line-length
contract Dividends is Pausable {
using SafeMath for uint256;
uint256 public constant YEARS = 5;
uint256 public constant RECLAIM_TIME = YEARS * 365 days;
address payable public wallet;
address public issuer;
/**
* @dev This is the internal counter which keeps track of the available dividends within each different payout token.
* Address 0 is used for ether balances
**/
mapping(address => uint256) public balances;
SygnumToken public sygToken;
Dividend[] public dividends;
event DividendDeposited(
address indexed depositor,
uint256 dividendIndex,
uint256 blockNumber,
uint256 amount,
bool isERC20
);
event DividendClaimed(address indexed claimer, uint256 dividendIndex, uint256 amount, bool isERC20);
event DividendRecycled(address indexed recycler, uint256 dividendIndex, uint256 amount);
event WalletUpdated(address indexed issuer, address indexed oldWallet, address indexed newWallet);
/** MODIFIERS */
modifier validDividendIndex(uint256 _dividendIndex) {
require(_dividendIndex < dividends.length, "invalid index");
_;
}
modifier onlyValidAddress(address _address) {
require(_address != address(0), "invalid address");
_;
}
modifier onlyIssuer() {
require(msg.sender == issuer, "sender is not issuer");
_;
}
/**
* @dev initialize dividend contract with the Sygnum Token contract address, issuer associated, and baseOperators contract address.
* @param _sygToken Address of the Sygnum Token.
* @param _issuer Address of the associated issuer to the Sygnum Token.
* @param _baseOperators Address of baseOperators address.
*/
constructor(
address _sygToken,
address payable _issuer,
address _baseOperators
) public onlyValidAddress(_sygToken) onlyValidAddress(_issuer) onlyValidAddress(_baseOperators) {
sygToken = SygnumToken(_sygToken);
wallet = _issuer;
issuer = _issuer;
super.initialize(_baseOperators);
}
/**
* @dev allows issuer to set a new wallet for reclaiming dividends
* @param _wallet New wallet address.
*/
function updateWallet(address payable _wallet) external onlyIssuer onlyValidAddress(_wallet) {
emit WalletUpdated(msg.sender, wallet, _wallet);
wallet = _wallet;
}
/**
* @dev deposit payoutDividend tokens (ERC20) into this contract
* @param _blockNumber uint256 Block height at which users' balances will be snapshot
* @param _exDividendDate uint256 Ex dividend date
* @param _recordDate uint256 Date when dividend was recorded
* @param _payoutDate uint256 Date when dividends will be able to be claimed
* @param _amount uint256 total amount of the ERC20 tokens deposited to payout to all token holders as of
* previous block from when this function is included
* @param _payoutToken ERC20 address of the token used for payout the current dividend
* @return uint256 dividendIndex index the dividend struct on the array
*/
function depositERC20Dividend(
uint256 _blockNumber,
uint256 _exDividendDate,
uint256 _recordDate,
uint256 _payoutDate,
uint256 _amount,
address _payoutToken
) public onlyIssuer whenNotPaused onlyValidAddress(_payoutToken) returns (uint256 dividendIndex) {
require(_amount > 0, "dividend amount !> 0");
require(_payoutDate > getNow(), "payoutDate !> now");
require(_blockNumber > block.number, "blockNum !> block.number");
require(
ERC20(_payoutToken).balanceOf(address(this)) >= balances[_payoutToken].add(_amount),
"issuer has not transferred amount"
);
uint256 supplyAtTimeOfDividend = sygToken.totalSupplyAt(_blockNumber);
balances[_payoutToken] = balances[_payoutToken].add(_amount);
dividendIndex = createDividend(
_blockNumber,
_exDividendDate,
_recordDate,
_payoutDate,
_amount,
supplyAtTimeOfDividend,
_payoutToken,
true
);
}
/**
* @dev deposit payoutDividend, in ether, into this contract
* @param _blockNumber uint256 Block height at which users' balances will be snapshot
* @param _exDividendDate uint256 Ex dividend date
* @param _recordDate uint256 Date when dividend was recorded
* @param _payoutDate uint256 Date when dividends will be able to be claimed
* @param _amount uint256 total amount of the ether deposited to payout to all token holders as of
* previous block from when this function is included
* @return uint256 dividendIndex index the dividend struct on the array
*/
function depositEtherDividend(
uint256 _blockNumber,
uint256 _exDividendDate,
uint256 _recordDate,
uint256 _payoutDate,
uint256 _amount
) public payable onlyIssuer whenNotPaused returns (uint256 dividendIndex) {
require(_amount > 0, "amount must be greater than 0");
require(msg.value == _amount, "ether sent != _amount");
require(_payoutDate > getNow(), "payoutDate !> now");
require(_blockNumber > block.number, "blockNum !> block.number");
uint256 supplyAtTimeOfDividend = sygToken.totalSupplyAt(_blockNumber);
balances[address(0)] = balances[address(0)].add(_amount);
dividendIndex = createDividend(
_blockNumber,
_exDividendDate,
_recordDate,
_payoutDate,
_amount,
supplyAtTimeOfDividend,
address(0),
false
);
}
/**
* @dev Sygnum token holder to claim their share of dividends at a specific divident index
* handles both ether and ERC20 token payouts
* @param _dividendIndex uint256 current index of the dividend to claim for msg.sender
*/
function claimDividend(uint256 _dividendIndex) public whenNotPaused validDividendIndex(_dividendIndex) {
Dividend storage dividend = dividends[_dividendIndex];
require(getNow() >= dividend.payoutDate, "too soon");
require(dividend.claimed[msg.sender] == false, "already claimed");
require(dividend.recycled == false, "already recycled");
require(getNow() < (dividend.payoutDate).add(RECLAIM_TIME), "time lapsed");
uint256 balance = sygToken.balanceOfAt(msg.sender, dividend.blockNumber);
require(balance > 0, "no dividends owed");
uint256 claimAmount = balance.mul(dividend.amount).div(dividend.totalSupply);
dividend.claimed[msg.sender] = true;
dividend.amountRemaining = dividend.amountRemaining.sub(claimAmount);
balances[dividend.payoutToken] = balances[dividend.payoutToken].sub(claimAmount);
if (dividend.isERC20Payout) {
ERC20 payoutToken = ERC20(dividend.payoutToken);
payoutToken.transfer(msg.sender, claimAmount);
} else {
msg.sender.transfer(claimAmount);
}
emit DividendClaimed(msg.sender, _dividendIndex, claimAmount, dividend.isERC20Payout);
}
/**
* @dev get dividend info at index
* @param _index uint256 Index of dividend in memory
* @return uint256, uint256, uint256, uint256, uint256, uint256, address, bool, uint256, bool
*/
function getDividend(uint256 _index)
public
view
validDividendIndex(_index)
returns (
uint256,
uint256,
uint256,
uint256,
uint256,
uint256,
address,
bool,
uint256,
bool
)
{
Dividend memory result = dividends[_index];
return (
result.blockNumber,
result.exDividendDate,
result.recordDate,
result.payoutDate,
result.amount,
result.totalSupply,
result.payoutToken,
result.isERC20Payout,
result.amountRemaining,
result.recycled
);
}
/**
* @dev onlyIssuer to recycle remaining amount to the specified wallet address stored on contract
* @param _dividendIndex unint256 index of the dividend to recycle
*/
function recycleDividend(uint256 _dividendIndex)
public
onlyIssuer
whenNotPaused
validDividendIndex(_dividendIndex)
{
Dividend storage dividend = dividends[_dividendIndex];
uint256 remainingAmount = dividend.amountRemaining;
require(dividend.recycled == false, "already recycled");
require(remainingAmount > 0, "nothing to recycle");
require(getNow() >= (dividend.payoutDate).add(RECLAIM_TIME), "too soon");
dividends[_dividendIndex].recycled = true;
balances[dividend.payoutToken] = balances[dividend.payoutToken].sub(remainingAmount);
dividend.amountRemaining = 0;
if (dividend.isERC20Payout) {
ERC20(dividend.payoutToken).transfer(wallet, remainingAmount);
} else {
wallet.transfer(remainingAmount);
}
emit DividendRecycled(msg.sender, _dividendIndex, remainingAmount);
}
/*** INTERNAL/PRIVATE ***/
/**
* @notice this creates a new dividend record that appends to the dividends array
* @param _blockNumber uint256 Block height at which users' balances will be snapshot
* @param _exDividendDate uint256 Ex dividend date
* @param _recordDate uint256 Date when dividend was recorded
* @param _payoutDate uint256 Date when dividends will be able to be claimed
* @param _amount uint256 total amount of the ether deposited to payout to all token holders as of
* previous block from when this function is included
* @param _totalSupply uint256 Total token supply at the time the dividend was created
* @param _payoutToken address Address of the ERC20 token that will be used for dividends payout
* @param _isERC20Payout bool If false, dividends will be in Ether
* @return uint256 dividendIndex
*/
function createDividend(
uint256 _blockNumber,
uint256 _exDividendDate,
uint256 _recordDate,
uint256 _payoutDate,
uint256 _amount,
uint256 _totalSupply,
address _payoutToken,
bool _isERC20Payout
) internal returns (uint256 dividendIndex) {
dividends.push(
Dividend(
_blockNumber,
_exDividendDate,
_recordDate,
_payoutDate,
_amount,
_totalSupply,
_payoutToken,
_isERC20Payout,
_amount,
false
)
);
dividendIndex = dividends.length.sub(1);
emit DividendDeposited(msg.sender, dividendIndex, _blockNumber, _amount, _isERC20Payout);
}
/**
* @dev use function to get timestamp to avoid excessive comments to disable solhint
* @return uint256
*/
function getNow() internal view returns (uint256) {
// solhint-disable-next-line
return block.timestamp;
}
struct Dividend {
uint256 blockNumber; // (Mandatory) block number used to check balance against
uint256 exDividendDate; // (Optional) On (or after) this date the security trades without its dividend
uint256 recordDate; // (Optional) This is the date on which the company looks at its records to see who the token holders are (~=blockNumber)
uint256 payoutDate; // (Mandatory) This is the date when payouts can be claimed
uint256 amount; // (Mandatory) total amount of the payout
uint256 totalSupply; // (Mandatory) total supply of the Sygnum Token at dividend instantiation
address payoutToken; // (Optional) allow each dividend period the flexibility for a different ERC20 token - can be address(0x0)
bool isERC20Payout; // (Mandatory) marks if this dividend is paid out in ether or ERC20
uint256 amountRemaining; // amount remaining to be paid out
bool recycled; // marks if dividend has been clawed back by the company
mapping(address => bool) claimed; // marks address as claimed once claimed has succeeded
}
}