Skip to content

Commit

Permalink
Merge d8827f1 into 4570480
Browse files Browse the repository at this point in the history
  • Loading branch information
VictorVicente committed Sep 28, 2018
2 parents 4570480 + d8827f1 commit b3d025c
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 50 deletions.
176 changes: 142 additions & 34 deletions CLI/commands/dividends_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async function start_explorer(){
let currentCheckpoint = await securityToken.methods.currentCheckpointId().call();
console.log(chalk.yellow(`\nToken is at checkpoint: ${currentCheckpoint}`));

let options = ['Mint tokens', 'Transfer tokens', 'Create checkpoint', 'Create dividends']
let options = ['Mint tokens', 'Transfer tokens', 'Create checkpoint', 'Set default exclusions for dividends', 'Tax holding settings', 'Create dividends']

if (currentCheckpoint > 0) {
options.push('Explore account at checkpoint', 'Explore total supply at checkpoint')
Expand All @@ -108,54 +108,52 @@ async function start_explorer(){
}

let index = readlineSync.keyInSelect(options, 'What do you want to do?');
console.log('Selected:', index != -1 ? options[index] : 'Cancel', '\n');
switch (index) {
case 0:
// Mint tokens
let selected = index != -1 ? options[index] : 'Cancel';
console.log('Selected:', selected, '\n');
switch (selected) {
case 'Mint tokens':
let _to = readlineSync.question('Enter beneficiary of minting: ');
let _amount = readlineSync.question('Enter amount of tokens to mint: ');
await mintTokens(_to,_amount);
break;
case 1:
// Transfer tokens
case 'Transfer tokens':
let _to2 = readlineSync.question('Enter beneficiary of tranfer: ');
let _amount2 = readlineSync.question('Enter amount of tokens to transfer: ');
await transferTokens(_to2,_amount2);
break;
case 2:
// Create checkpoint
case 'Create checkpoint':
let createCheckpointAction = securityToken.methods.createCheckpoint();
await common.sendTransaction(Issuer, createCheckpointAction, defaultGasPrice);
break;
case 3:
// Create Dividends
case 'Set default exclusions for dividends':
await setDefaultExclusions();
break;
case 'Tax holding settings':
await taxHoldingMenu();
break;
case 'Create dividends':
let dividend = readlineSync.question(`How much ${dividendsType} would you like to distribute to token holders?: `);
await checkBalance(dividend);
let checkpointId = currentCheckpoint == 0 ? 0 : await selectCheckpoint(true); // If there are no checkpoints, it must create a new one
await createDividends(dividend, checkpointId);
break;
case 4:
// Explore account at checkpoint
case 'Explore account at checkpoint':
let _address = readlineSync.question('Enter address to explore: ');
let _checkpoint = await selectCheckpoint(false);
await exploreAddress(_address, _checkpoint);
break;
case 5:
// Explore total supply at checkpoint
case 'Explore total supply at checkpoint':
let _checkpoint2 = await selectCheckpoint(false);
await exploreTotalSupply(_checkpoint2);
break;
break;
case 6:
// Push dividends to account
let _dividend = await selectDividend({valid: true, expired: false, reclaimed: false});
case 'Push dividends to accounts':
let _dividend = await selectDividend({valid: true, expired: false, reclaimed: false, withRemaining: true});
if (_dividend !== null) {
let _addresses = readlineSync.question('Enter addresses to push dividends to (ex- add1,add2,add3,...): ');
await pushDividends(_dividend, _addresses);
}
break;
case 7:
//explore balance
case `Explore ${dividendsType} balance`:
let _address3 = readlineSync.question('Enter address to explore: ');
let _dividend3 = await selectDividend();
if (_dividend3 !== null) {
Expand All @@ -167,14 +165,13 @@ async function start_explorer(){
`);
}
break;
case 8:
// Reclaimed dividends after expiry
case 'Reclaim expired dividends':
let _dividend4 = await selectDividend({expired: true, reclaimed: false});
if (_dividend4 !== null) {
await reclaimedDividend(_dividend4);
}
break;
case -1:
case 'Cancel':
process.exit(0);
break;
}
Expand Down Expand Up @@ -248,33 +245,116 @@ async function exploreTotalSupply(checkpoint){
console.log(`TotalSupply is: ${totalSupplyAt} (Using totalSupplyAt - checkpoint ${checkpoint})`);
}

async function setDefaultExclusions() {
await addDividendsModule();

let excluded = await currentDividendsModule.methods.getDefaultExcluded().call();
showExcluded(excluded);

console.log(chalk.yellow(`Excluded addresses will be loaded from 'dividendsExclusions_data.csv'. Please check your data before continue.`));
if (readlineSync.keyInYNStrict(`Do you want to continue?`)) {
let excluded = getExcludedFromDataFile();
let setDefaultExclusionsActions = currentDividendsModule.methods.setDefaultExcluded(excluded);
let receipt = await common.sendTransaction(Issuer, setDefaultExclusionsActions, defaultGasPrice);
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetDefaultExcludedAddresses');
console.log(chalk.green(`Exclusions were successfuly set.`));
showExcluded(event._excluded);
}
}

async function taxHoldingMenu() {
await addDividendsModule();

let options = ['Set a % to withhold from dividends sent to an address', 'Withdraw withholding for dividend', 'Return to main menu'];
let index = readlineSync.keyInSelect(options, 'What do you want to do?', {cancel: false});
let selected = options[index];
console.log("Selected:", selected);
switch (selected) {
case 'Set a % to withhold from dividends sent to an address':
let address = readlineSync.question('Enter the address of the investor: ', {
limit: function(input) {
return web3.utils.isAddress(input);
},
limitMessage: "Must be a valid address",
});
let percentage = readlineSync.question('Enter the percentage of dividends to withhold (number between 0-100): ', {
limit: function(input) {
return (parseInt(input) >= 0 && parseInt(input) <= 100);
},
limitMessage: "Must be a value between 0 and 100",
});
let percentageWei = web3.utils.toWei((percentage / 100).toString());
let setWithHoldingFixedAction = currentDividendsModule.methods.setWithholdingFixed([address], percentageWei);
let receipt = await common.sendTransaction(Issuer, setWithHoldingFixedAction, defaultGasPrice);
console.log(chalk.green(`Successfully set tax withholding of ${percentage}% for ${address}.`));
break;
case 'Withdraw withholding for dividend':
let _dividend = await selectDividend({withRemainingWithheld: true});
if (_dividend !== null) {
let withdrawWithholdingAction = currentDividendsModule.methods.withdrawWithholding(_dividend.index);
let receipt = await common.sendTransaction(Issuer, withdrawWithholdingAction, defaultGasPrice);
let eventName;
if (dividendsType == 'POLY') {
eventName = 'ERC20DividendWithholdingWithdrawn';
} else if (dividendsType == 'ETH') {
eventName = 'EtherDividendWithholdingWithdrawn';
}
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, eventName);
console.log(chalk.green(`Successfully withdrew ${web3.utils.fromWei(event._withheldAmount)} ${dividendsType} from dividend ${_dividend.index} tax withholding.`));
}
break;
case 'Return to main menu':
break;
}
}

async function createDividends(dividend, checkpointId) {
await addDividendsModule();

let time = Math.floor(Date.now()/1000);
let maturityTime = readlineSync.questionInt('Enter the dividend maturity time from which dividend can be paid (Unix Epoch time)\n(Now = ' + time + ' ): ', {defaultInput: time});
let defaultTime = time + duration.minutes(10);
let expiryTime = readlineSync.questionInt('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = ' + defaultTime + ' ): ', {defaultInput: defaultTime});

let useDefaultExcluded = readlineSync.keyInYNStrict(`Do you want to use the default excluded addresses for this dividend? If not, data from 'dividendsExclusions_data.csv' will be used instead.`);

let createDividendAction;
if (dividendsType == 'POLY') {
let approveAction = polyToken.methods.approve(currentDividendsModule._address, web3.utils.toWei(dividend));
await common.sendTransaction(Issuer, approveAction, defaultGasPrice);
if (checkpointId > 0) {
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), checkpointId);
if (useDefaultExcluded) {
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), checkpointId);
} else {
let excluded = getExcludedFromDataFile();
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), checkpointId, excluded);
}
} else {
createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend));
if (useDefaultExcluded) {
createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend));
} else {
let excluded = getExcludedFromDataFile();
createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), excluded);
}
}
let receipt = await common.sendTransaction(Issuer, createDividendAction, defaultGasPrice);
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'ERC20DividendDeposited');
console.log(`
Dividend ${event._dividendIndex} deposited`
);
console.log(chalk.green(`Dividend ${event._dividendIndex} deposited`));
} else if (dividendsType == 'ETH') {
if (checkpointId > 0) {
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, checkpointId);
if (useDefaultExcluded) {
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, checkpointId);
} else {
let excluded = getExcludedFromDataFile();
createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, checkpointId, excluded);
}
} else {
createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime);
if (useDefaultExcluded) {
createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime);
} else {
let excluded = getExcludedFromDataFile();
createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded);
}
}
let receipt = await common.sendTransaction(Issuer, createDividendAction, defaultGasPrice, web3.utils.toWei(dividend));
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'EtherDividendDeposited');
Expand Down Expand Up @@ -307,7 +387,8 @@ async function pushDividends(dividend, account){
for (const event of successEvents) {
console.log(`
Claimed ${web3.utils.fromWei(event._amount)} ${dividendsType}
to account ${event._payee}`
to account ${event._payee}
${web3.utils.fromWei(event._withheld)} ${dividendsType} of tax withheld`
);
}
}
Expand Down Expand Up @@ -449,16 +530,24 @@ async function selectDividend(filter) {
if (typeof filter.reclaimed !== 'undefined') {
dividends = dividends.filter(d => filter.reclaimed == d.reclaimed);
}
if (typeof filter.withRemainingWithheld !== 'undefined') {
dividends = dividends.filter(d => new web3.utils.BN(d.dividendWithheld).sub(new web3.utils.BN(d.dividendWithheldReclaimed)) > 0);
}
if (typeof filter.withRemaining !== 'undefined') {
dividends = dividends.filter(d => new web3.utils.BN(d.amount).sub(new web3.utils.BN(d.claimedAmount)) > 0);
}
}

if (dividends.length > 0) {
let options = dividends.map(function(d) {
return `Created: ${moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')}
Maturity: ${moment.unix(d.maturity).format('MMMM Do YYYY, HH:mm:ss')}
Expiry: ${moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')}
At checkpoint: ${d.checkpointId}
Amount: ${web3.utils.fromWei(d.amount)} ${dividendsType}
Claimed Amount: ${web3.utils.fromWei(d.claimedAmount)} ${dividendsType}
At checkpoint: ${d.checkpointId}`
Withheld: ${web3.utils.fromWei(d.dividendWithheld)} ${dividendsType}
Withheld claimed: ${web3.utils.fromWei(d.dividendWithheldReclaimed)} ${dividendsType}`
});

let index = readlineSync.keyInSelect(options, 'Select a dividend:');
Expand All @@ -467,7 +556,8 @@ async function selectDividend(filter) {
}
} else {
console.log(chalk.red(`No dividends were found meeting the requirements`))
console.log(chalk.red(`Requirements: Valid: ${filter.valid} - Expired: ${filter.expired} - Reclaimed: ${filter.reclaimed}\n`))
console.log(chalk.red(`Requirements: Valid: ${filter.valid} - Expired: ${filter.expired} - Reclaimed: ${filter.reclaimed}
WithRemainingWithheld: ${filter.withRemainingWithheld} - WithRemaining: ${filter.withRemaining}\n`))
}

return result;
Expand All @@ -489,6 +579,24 @@ async function getDividends() {
return result;
}

function getExcludedFromDataFile() {
let excludedFromFile = require('fs').readFileSync('./CLI/data/dividendsExclusions_data.csv').toString().split("\n");
let excluded = excludedFromFile.filter(function (address) {
return web3.utils.isAddress(address);
});
return excluded;
}

function showExcluded(excluded) {
if (excluded.length > 0) {
console.log('Current default excluded addresses:')
excluded.map(function (address) { console.log(' ', address) });
} else {
console.log('There are not default excluded addresses.')
}
console.log();
}

module.exports = {
executeApp: async function(type, remoteNetwork) {
return executeApp(type, remoteNetwork);
Expand Down
6 changes: 6 additions & 0 deletions CLI/data/dividendsExclusions_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
0xa26cc0567e29fda3c77369791e926b4c46456fcb
0x97c09f763a699a51cb947f95e602c08f7bcba202
0x308a3ea530e5b160f1f7115f2ddb30cb44d8b54d
0xbc1bfb5d90692854672febe76ca5379dea1015e4
0x7442dcd2eb074ea6dbfaa8338300bc86238c2aae
0x0a519b4b6501f92e8f516230b97aca83257b0c01
40 changes: 26 additions & 14 deletions contracts/modules/Checkpoint/DividendCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ contract DividendCheckpoint is ICheckpoint, Module {
// Total amount of ETH withheld per investor
mapping (address => uint256) public investorWithheld;

event SetExcludedAddresses(address[] _excluded, uint256 _timestamp);
event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp);
event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp);
event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp);

modifier validDividendIndex(uint256 _dividendIndex) {
require(_dividendIndex < dividends.length, "Incorrect dividend index");
Expand All @@ -62,26 +64,35 @@ contract DividendCheckpoint is ICheckpoint, Module {
}

/**
* @notice Function to set withholding tax rates for investors
* @param _investors addresses of investor
* @param _withholding withholding tax for individual investors (multiplied by 10**16)
* @notice Return the default excluded addresses
* @return List of excluded addresses
*/
function setWithholding(address[] _investors, uint256[] _withholding) public onlyOwner {
require(_investors.length == _withholding.length, "Mismatched input lengths");
for (uint256 i = 0; i < _investors.length; i++) {
require(_withholding[i] <= 10**18);
withholdingTax[_investors[i]] = _withholding[i];
}
function getDefaultExcluded() external view returns (address[]) {
return excluded;
}

/**
* @notice Function to clear and set list of excluded addresses used for future dividends
* @param _excluded addresses of investor
*/
function setExcluded(address[] _excluded) public onlyOwner {
function setDefaultExcluded(address[] _excluded) public onlyOwner {
require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses");
excluded = _excluded;
emit SetExcludedAddresses(excluded, now);
emit SetDefaultExcludedAddresses(excluded, now);
}

/**
* @notice Function to set withholding tax rates for investors
* @param _investors addresses of investor
* @param _withholding withholding tax for individual investors (multiplied by 10**16)
*/
function setWithholding(address[] _investors, uint256[] _withholding) public onlyOwner {
require(_investors.length == _withholding.length, "Mismatched input lengths");
emit SetWithholding(_investors, _withholding, now);
for (uint256 i = 0; i < _investors.length; i++) {
require(_withholding[i] <= 10**18, "Incorrect withholding tax");
withholdingTax[_investors[i]] = _withholding[i];
}
}

/**
Expand All @@ -90,7 +101,8 @@ contract DividendCheckpoint is ICheckpoint, Module {
* @param _withholding withholding tax for all investors (multiplied by 10**16)
*/
function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner {
require(_withholding <= 10**18);
require(_withholding <= 10**18, "Incorrect withholding tax");
emit SetWithholdingFixed(_investors, _withholding, now);
for (uint256 i = 0; i < _investors.length; i++) {
withholdingTax[_investors[i]] = _withholding;
}
Expand Down Expand Up @@ -157,7 +169,7 @@ contract DividendCheckpoint is ICheckpoint, Module {
* @notice Calculate amount of dividends claimable
* @param _dividendIndex Dividend to calculate
* @param _payee Affected investor address
* @return unit256
* @return claim, withheld amounts
*/
function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) {
require(_dividendIndex < dividends.length, "Incorrect dividend index");
Expand Down
4 changes: 2 additions & 2 deletions test/e_erc20_dividends.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ contract('ERC20DividendCheckpoint', accounts => {
});

it("Exclude account_temp using global exclusion list", async() => {
await I_ERC20DividendCheckpoint.setExcluded([account_temp], {from: token_owner});
await I_ERC20DividendCheckpoint.setDefaultExcluded([account_temp], {from: token_owner});
});

it("Create another new dividend", async() => {
Expand Down Expand Up @@ -655,7 +655,7 @@ contract('ERC20DividendCheckpoint', accounts => {


it("Delete global exclusion list", async() => {
await I_ERC20DividendCheckpoint.setExcluded([], {from: token_owner});
await I_ERC20DividendCheckpoint.setDefaultExcluded([], {from: token_owner});
});


Expand Down

0 comments on commit b3d025c

Please sign in to comment.