Skip to content

Commit 83f0afa

Browse files
authored
Dividend improvements (#526)
* Working * Wallet change test case * Fix ether dividend tests * CLI - Update for wallet on dividends modules * Add estimated tax / amounts to getter * CLI - Update for dividends status data
1 parent 688f0a4 commit 83f0afa

11 files changed

+211
-79
lines changed

CLI/commands/dividends_manager.js

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,16 @@ async function configExistingModules(dividendModules) {
123123
async function dividendsManager() {
124124
console.log(chalk.blue(`Dividends module at ${currentDividendsModule.options.address}`), '\n');
125125

126+
let wallet = await currentDividendsModule.methods.wallet().call();
126127
let currentDividends = await getDividends();
127128
let defaultExcluded = await currentDividendsModule.methods.getDefaultExcluded().call();
128129
let currentCheckpointId = await securityToken.methods.currentCheckpointId().call();
129130

131+
console.log(`- Wallet: ${wallet}`);
130132
console.log(`- Current dividends: ${currentDividends.length}`);
131133
console.log(`- Default exclusions: ${defaultExcluded.length}`);
132134

133-
let options = ['Create checkpoint'];
135+
let options = ['Change wallet', 'Create checkpoint'];
134136
if (currentCheckpointId > 0) {
135137
options.push('Explore checkpoint');
136138
}
@@ -150,6 +152,9 @@ async function dividendsManager() {
150152
let selected = index != -1 ? options[index] : 'RETURN';
151153
console.log('Selected:', selected, '\n');
152154
switch (selected) {
155+
case 'Change wallet':
156+
await changeWallet();
157+
break;
153158
case 'Create checkpoint':
154159
await createCheckpointFromDividendModule();
155160
break;
@@ -180,6 +185,19 @@ async function dividendsManager() {
180185
await dividendsManager();
181186
}
182187

188+
async function changeWallet() {
189+
let newWallet = readlineSync.question('Enter the new account address to receive reclaimed dividends and tax: ', {
190+
limit: function (input) {
191+
return web3.utils.isAddress(input);
192+
},
193+
limitMessage: "Must be a valid address",
194+
});
195+
let action = currentDividendsModule.methods.changeWallet(newWallet);
196+
let receipt = await common.sendTransaction(action);
197+
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetWallet');
198+
console.log(chalk.green(`The wallet has been changed successfully!`));
199+
}
200+
183201
async function createCheckpointFromDividendModule() {
184202
let createCheckpointAction = securityToken.methods.createCheckpoint();
185203
await common.sendTransaction(createCheckpointAction);
@@ -230,8 +248,19 @@ async function manageExistingDividend(dividendIndex) {
230248
let claimedArray = progress[1];
231249
let excludedArray = progress[2];
232250
let withheldArray = progress[3];
233-
let balanceArray = progress[4];
234-
let amountArray = progress[5];
251+
let amountArray = progress[4];
252+
let balanceArray = progress[5];
253+
254+
// function for adding two numbers. Easy!
255+
const add = (a, b) => {
256+
const bnA = new web3.utils.BN(a);
257+
const bnB = new web3.utils.BN(b);
258+
return bnA.add(bnB).toString();
259+
};
260+
// use reduce to sum our array
261+
let taxesToWithHeld = withheldArray.reduce(add, 0);
262+
let claimedInvestors = claimedArray.filter(c => c).length;
263+
let excludedInvestors = excludedArray.filter(e => e).length;
235264

236265
console.log(`- Name: ${web3.utils.hexToUtf8(dividend.name)}`);
237266
console.log(`- Created: ${moment.unix(dividend.created).format('MMMM Do YYYY, HH:mm:ss')}`);
@@ -240,11 +269,13 @@ async function manageExistingDividend(dividendIndex) {
240269
console.log(`- At checkpoint: ${dividend.checkpointId}`);
241270
console.log(`- Amount: ${web3.utils.fromWei(dividend.amount)} ${dividendTokenSymbol}`);
242271
console.log(`- Claimed amount: ${web3.utils.fromWei(dividend.claimedAmount)} ${dividendTokenSymbol}`);
243-
console.log(`- Withheld: ${web3.utils.fromWei(dividend.totalWithheld)} ${dividendTokenSymbol}`);
244-
console.log(`- Withheld claimed: ${web3.utils.fromWei(dividend.totalWithheldWithdrawn)} ${dividendTokenSymbol}`);
272+
console.log(`- Taxes:`);
273+
console.log(` To withhold: ${web3.utils.fromWei(taxesToWithHeld)} ${dividendTokenSymbol}`);
274+
console.log(` Withheld to-date: ${web3.utils.fromWei(dividend.totalWithheld)} ${dividendTokenSymbol}`);
275+
console.log(` Withdrawn to-date: ${web3.utils.fromWei(dividend.totalWithheldWithdrawn)} ${dividendTokenSymbol}`);
245276
console.log(`- Total investors: ${investorArray.length}`);
246-
console.log(` Have already claimed: ${claimedArray.filter(c => c).length}`);
247-
console.log(` Excluded: ${excludedArray.filter(e => e).length} `);
277+
console.log(` Have already claimed: ${claimedInvestors} (${investorArray.length - excludedInvestors !== 0 ? claimedInvestors / (investorArray.length - excludedInvestors) * 100 : 0}%)`);
278+
console.log(` Excluded: ${excludedInvestors} `);
248279
// ------------------
249280

250281

@@ -454,10 +485,10 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo
454485
let investor = _investorArray[i];
455486
let excluded = _excludedArray[i];
456487
let withdrawn = _claimedArray[i] ? 'YES' : 'NO';
457-
let amount = !excluded ? web3.utils.fromWei(_amountArray[i]) : 0;
458-
let withheld = (!excluded && _claimedArray[i]) ? web3.utils.fromWei(_withheldArray[i]) : 'NA';
459-
let withheldPercentage = (!excluded && _claimedArray[i]) ? (withheld !== '0' ? parseFloat(withheld) / parseFloat(amount) * 100 : 0) : 'NA';
460-
let received = (!excluded && _claimedArray[i]) ? web3.utils.fromWei(web3.utils.toBN(_amountArray[i]).sub(web3.utils.toBN(_withheldArray[i]))) : 0;
488+
let amount = !excluded ? web3.utils.fromWei(web3.utils.toBN(_amountArray[i]).add(web3.utils.toBN(_withheldArray[i]))) : 0;
489+
let withheld = !excluded ? web3.utils.fromWei(_withheldArray[i]) : 'NA';
490+
let withheldPercentage = (!excluded) ? (withheld !== '0' ? parseFloat(withheld) / parseFloat(amount) * 100 : 0) : 'NA';
491+
let received = !excluded ? web3.utils.fromWei(_amountArray[i]) : 0;
461492
dataTable.push([
462493
investor,
463494
amount,
@@ -476,6 +507,8 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo
476507
console.log(`- Total amount of investors: ${_investorArray.length} `);
477508
console.log();
478509
console.log(table(dataTable));
510+
console.log();
511+
console.log(chalk.yellow(`NOTE: If investor has not claimed the dividend yet, TAX and AMOUNT are calculated with the current values set and they might change.`));
479512
console.log(chalk.yellow(`-----------------------------------------------------------------------------------------------------------------------------------------------------------`));
480513
console.log();
481514
}
@@ -561,7 +594,14 @@ async function addDividendsModule() {
561594

562595
let index = readlineSync.keyInSelect(options, 'Which dividends module do you want to add? ', { cancel: 'Return' });
563596
if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]} module? `)) {
564-
let bytes = web3.utils.fromAscii('', 16);
597+
let wallet = readlineSync.question('Enter the account address to receive reclaimed dividends and tax: ', {
598+
limit: function (input) {
599+
return web3.utils.isAddress(input);
600+
},
601+
limitMessage: "Must be a valid address",
602+
});
603+
let configureFunction = abis.erc20DividendCheckpoint().find(o => o.name === 'configure' && o.type === 'function');
604+
let bytes = web3.eth.abi.encodeFunctionCall(configureFunction, [wallet]);
565605

566606
let selectedDividendFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.DIVIDENDS, options[index]);
567607
let addModuleAction = securityToken.methods.addModule(selectedDividendFactoryAddress, bytes, 0, 0);
@@ -655,11 +695,11 @@ async function selectDividend(dividends) {
655695
let result = null;
656696
let options = dividends.map(function (d) {
657697
return `${d.name}
658-
Amount: ${ web3.utils.fromWei(d.amount)} ${dividendsType}
659-
Status: ${ isExpiredDividend(d) ? 'Expired' : hasRemaining(d) ? 'In progress' : 'Completed'}
660-
Token: ${ d.tokenSymbol}
661-
Created: ${ moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')}
662-
Expiry: ${ moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} `
698+
Amount: ${web3.utils.fromWei(d.amount)} ${d.tokenSymbol}
699+
Status: ${isExpiredDividend(d) ? 'Expired' : hasRemaining(d) ? 'In progress' : 'Completed'}
700+
Token: ${d.tokenSymbol}
701+
Created: ${moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')}
702+
Expiry: ${moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} `
663703
});
664704

665705
let index = readlineSync.keyInSelect(options, 'Select a dividend:', { cancel: 'RETURN' });

contracts/modules/Checkpoint/DividendCheckpoint.sol

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
2424
event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp);
2525
event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp);
2626
event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp);
27+
event SetWallet(address indexed _oldWallet, address indexed _newWallet, uint256 _timestamp);
2728

2829
modifier validDividendIndex(uint256 _dividendIndex) {
2930
require(_dividendIndex < dividends.length, "Invalid dividend");
@@ -35,12 +36,36 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
3536
_;
3637
}
3738

39+
/**
40+
* @notice Function used to intialize the contract variables
41+
* @param _wallet Ethereum account address to receive reclaimed dividends and tax
42+
*/
43+
function configure(
44+
address _wallet
45+
) public onlyFactory {
46+
_setWallet(_wallet);
47+
}
48+
3849
/**
3950
* @notice Init function i.e generalise function to maintain the structure of the module contract
4051
* @return bytes4
4152
*/
4253
function getInitFunction() public pure returns (bytes4) {
43-
return bytes4(0);
54+
return this.configure.selector;
55+
}
56+
57+
/**
58+
* @notice Function used to change wallet address
59+
* @param _wallet Ethereum account address to receive reclaimed dividends and tax
60+
*/
61+
function changeWallet(address _wallet) external onlyOwner {
62+
_setWallet(_wallet);
63+
}
64+
65+
function _setWallet(address _wallet) internal {
66+
require(_wallet != address(0));
67+
emit SetWallet(wallet, _wallet, now);
68+
wallet = _wallet;
4469
}
4570

4671
/**
@@ -286,17 +311,17 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
286311
* @return address[] list of investors
287312
* @return bool[] whether investor has claimed
288313
* @return bool[] whether investor is excluded
289-
* @return uint256[] amount of withheld tax
314+
* @return uint256[] amount of withheld tax (estimate if not claimed)
315+
* @return uint256[] amount of claim (estimate if not claimeed)
290316
* @return uint256[] investor balance
291-
* @return uint256[] amount to be claimed including withheld tax
292317
*/
293318
function getDividendProgress(uint256 _dividendIndex) external view returns (
294319
address[] memory investors,
295320
bool[] memory resultClaimed,
296321
bool[] memory resultExcluded,
297322
uint256[] memory resultWithheld,
298-
uint256[] memory resultBalance,
299-
uint256[] memory resultAmount)
323+
uint256[] memory resultAmount,
324+
uint256[] memory resultBalance)
300325
{
301326
require(_dividendIndex < dividends.length, "Invalid dividend");
302327
//Get list of Investors
@@ -306,15 +331,21 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
306331
resultClaimed = new bool[](investors.length);
307332
resultExcluded = new bool[](investors.length);
308333
resultWithheld = new uint256[](investors.length);
309-
resultBalance = new uint256[](investors.length);
310334
resultAmount = new uint256[](investors.length);
335+
resultBalance = new uint256[](investors.length);
311336
for (uint256 i; i < investors.length; i++) {
312337
resultClaimed[i] = dividend.claimed[investors[i]];
313338
resultExcluded[i] = dividend.dividendExcluded[investors[i]];
314339
resultBalance[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], dividend.checkpointId);
315340
if (!resultExcluded[i]) {
316-
resultWithheld[i] = dividend.withheld[investors[i]];
317-
resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply);
341+
if (resultClaimed[i]) {
342+
resultWithheld[i] = dividend.withheld[investors[i]];
343+
resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply).sub(resultWithheld[i]);
344+
} else {
345+
(uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, investors[i]);
346+
resultWithheld[i] = withheld;
347+
resultAmount[i] = claim.sub(withheld);
348+
}
318349
}
319350
}
320351
}

contracts/modules/Checkpoint/DividendCheckpointStorage.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pragma solidity ^0.4.24;
66
*/
77
contract DividendCheckpointStorage {
88

9+
// Address to which reclaimed dividends and withholding tax is sent
10+
address public wallet;
911
uint256 public EXCLUDED_ADDRESS_LIMIT = 150;
1012
bytes32 public constant DISTRIBUTE = "DISTRIBUTE";
1113
bytes32 public constant MANAGE = "MANAGE";

contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,8 @@ contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendChec
261261
dividends[_dividendIndex].reclaimed = true;
262262
Dividend storage dividend = dividends[_dividendIndex];
263263
uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount);
264-
address owner = IOwnable(securityToken).owner();
265-
require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingAmount), "transfer failed");
266-
emit ERC20DividendReclaimed(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount);
264+
require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingAmount), "transfer failed");
265+
emit ERC20DividendReclaimed(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount);
267266
}
268267

269268
/**
@@ -275,9 +274,8 @@ contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendChec
275274
Dividend storage dividend = dividends[_dividendIndex];
276275
uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn);
277276
dividend.totalWithheldWithdrawn = dividend.totalWithheld;
278-
address owner = IOwnable(securityToken).owner();
279-
require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingWithheld), "transfer failed");
280-
emit ERC20DividendWithholdingWithdrawn(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld);
277+
require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingWithheld), "transfer failed");
278+
emit ERC20DividendWithholdingWithdrawn(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld);
281279
}
282280

283281
}

contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
pragma solidity ^0.4.24;
22

33
import "../../proxy/ERC20DividendCheckpointProxy.sol";
4+
import "../../libraries/Util.sol";
5+
import "../../interfaces/IBoot.sol";
46
import "../ModuleFactory.sol";
57

68
/**
@@ -35,10 +37,14 @@ contract ERC20DividendCheckpointFactory is ModuleFactory {
3537
* @notice Used to launch the Module with the help of factory
3638
* @return Address Contract address of the Module
3739
*/
38-
function deploy(bytes /* _data */) external returns(address) {
40+
function deploy(bytes _data) external returns(address) {
3941
if (setupCost > 0)
4042
require(polyToken.transferFrom(msg.sender, owner, setupCost), "insufficent allowance");
4143
address erc20DividendCheckpoint = new ERC20DividendCheckpointProxy(msg.sender, address(polyToken), logicContract);
44+
//Checks that _data is valid (not calling anything it shouldn't)
45+
require(Util.getSig(_data) == IBoot(erc20DividendCheckpoint).getInitFunction(), "Invalid data");
46+
/*solium-disable-next-line security/no-low-level-calls*/
47+
require(erc20DividendCheckpoint.call(_data), "Unsuccessfull call");
4248
/*solium-disable-next-line security/no-block-members*/
4349
emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now);
4450
return erc20DividendCheckpoint;

contracts/modules/Checkpoint/EtherDividendCheckpoint.sol

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,8 @@ contract EtherDividendCheckpoint is DividendCheckpoint {
200200
Dividend storage dividend = dividends[_dividendIndex];
201201
dividend.reclaimed = true;
202202
uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount);
203-
address owner = IOwnable(securityToken).owner();
204-
owner.transfer(remainingAmount);
205-
emit EtherDividendReclaimed(owner, _dividendIndex, remainingAmount);
203+
wallet.transfer(remainingAmount);
204+
emit EtherDividendReclaimed(wallet, _dividendIndex, remainingAmount);
206205
}
207206

208207
/**
@@ -214,9 +213,8 @@ contract EtherDividendCheckpoint is DividendCheckpoint {
214213
Dividend storage dividend = dividends[_dividendIndex];
215214
uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn);
216215
dividend.totalWithheldWithdrawn = dividend.totalWithheld;
217-
address owner = IOwnable(securityToken).owner();
218-
owner.transfer(remainingWithheld);
219-
emit EtherDividendWithholdingWithdrawn(owner, _dividendIndex, remainingWithheld);
216+
wallet.transfer(remainingWithheld);
217+
emit EtherDividendWithholdingWithdrawn(wallet, _dividendIndex, remainingWithheld);
220218
}
221219

222220
}

contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
pragma solidity ^0.4.24;
22

33
import "../../proxy/EtherDividendCheckpointProxy.sol";
4+
import "../../libraries/Util.sol";
5+
import "../../interfaces/IBoot.sol";
46
import "../ModuleFactory.sol";
57

68
/**
@@ -35,10 +37,14 @@ contract EtherDividendCheckpointFactory is ModuleFactory {
3537
* @notice Used to launch the Module with the help of factory
3638
* @return address Contract address of the Module
3739
*/
38-
function deploy(bytes /* _data */) external returns(address) {
40+
function deploy(bytes _data) external returns(address) {
3941
if(setupCost > 0)
4042
require(polyToken.transferFrom(msg.sender, owner, setupCost), "Insufficent allowance or balance");
4143
address ethDividendCheckpoint = new EtherDividendCheckpointProxy(msg.sender, address(polyToken), logicContract);
44+
//Checks that _data is valid (not calling anything it shouldn't)
45+
require(Util.getSig(_data) == IBoot(ethDividendCheckpoint).getInitFunction(), "Invalid data");
46+
/*solium-disable-next-line security/no-low-level-calls*/
47+
require(ethDividendCheckpoint.call(_data), "Unsuccessfull call");
4248
/*solium-disable-next-line security/no-block-members*/
4349
emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now);
4450
return ethDividendCheckpoint;

contracts/modules/STO/USDTieredSTO.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,6 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard {
199199
address _reserveWallet,
200200
address[] _usdTokens
201201
) external onlyOwner {
202-
/*solium-disable-next-line security/no-block-members*/
203-
// require(now < startTime, "STO already started");
204202
_modifyAddresses(_wallet, _reserveWallet, _usdTokens);
205203
}
206204

contracts/modules/STO/USDTieredSTOFactory.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ contract USDTieredSTOFactory is ModuleFactory {
4242
//Checks that _data is valid (not calling anything it shouldn't)
4343
require(Util.getSig(_data) == IBoot(usdTieredSTO).getInitFunction(), "Invalid data");
4444
/*solium-disable-next-line security/no-low-level-calls*/
45-
require(address(usdTieredSTO).call(_data), "Unsuccessfull call");
45+
require(usdTieredSTO.call(_data), "Unsuccessfull call");
4646
/*solium-disable-next-line security/no-block-members*/
4747
emit GenerateModuleFromFactory(usdTieredSTO, getName(), address(this), msg.sender, setupCost, now);
48-
return address(usdTieredSTO);
48+
return usdTieredSTO;
4949
}
5050

5151
/**

0 commit comments

Comments
 (0)