Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 57 additions & 17 deletions CLI/commands/dividends_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,16 @@ async function configExistingModules(dividendModules) {
async function dividendsManager() {
console.log(chalk.blue(`Dividends module at ${currentDividendsModule.options.address}`), '\n');

let wallet = await currentDividendsModule.methods.wallet().call();
let currentDividends = await getDividends();
let defaultExcluded = await currentDividendsModule.methods.getDefaultExcluded().call();
let currentCheckpointId = await securityToken.methods.currentCheckpointId().call();

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

let options = ['Create checkpoint'];
let options = ['Change wallet', 'Create checkpoint'];
if (currentCheckpointId > 0) {
options.push('Explore checkpoint');
}
Expand All @@ -150,6 +152,9 @@ async function dividendsManager() {
let selected = index != -1 ? options[index] : 'RETURN';
console.log('Selected:', selected, '\n');
switch (selected) {
case 'Change wallet':
await changeWallet();
break;
case 'Create checkpoint':
await createCheckpointFromDividendModule();
break;
Expand Down Expand Up @@ -180,6 +185,19 @@ async function dividendsManager() {
await dividendsManager();
}

async function changeWallet() {
let newWallet = readlineSync.question('Enter the new account address to receive reclaimed dividends and tax: ', {
limit: function (input) {
return web3.utils.isAddress(input);
},
limitMessage: "Must be a valid address",
});
let action = currentDividendsModule.methods.changeWallet(newWallet);
let receipt = await common.sendTransaction(action);
let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetWallet');
console.log(chalk.green(`The wallet has been changed successfully!`));
}

async function createCheckpointFromDividendModule() {
let createCheckpointAction = securityToken.methods.createCheckpoint();
await common.sendTransaction(createCheckpointAction);
Expand Down Expand Up @@ -230,8 +248,19 @@ async function manageExistingDividend(dividendIndex) {
let claimedArray = progress[1];
let excludedArray = progress[2];
let withheldArray = progress[3];
let balanceArray = progress[4];
let amountArray = progress[5];
let amountArray = progress[4];
let balanceArray = progress[5];

// function for adding two numbers. Easy!
const add = (a, b) => {
const bnA = new web3.utils.BN(a);
const bnB = new web3.utils.BN(b);
return bnA.add(bnB).toString();
};
// use reduce to sum our array
let taxesToWithHeld = withheldArray.reduce(add, 0);
let claimedInvestors = claimedArray.filter(c => c).length;
let excludedInvestors = excludedArray.filter(e => e).length;

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


Expand Down Expand Up @@ -454,10 +485,10 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo
let investor = _investorArray[i];
let excluded = _excludedArray[i];
let withdrawn = _claimedArray[i] ? 'YES' : 'NO';
let amount = !excluded ? web3.utils.fromWei(_amountArray[i]) : 0;
let withheld = (!excluded && _claimedArray[i]) ? web3.utils.fromWei(_withheldArray[i]) : 'NA';
let withheldPercentage = (!excluded && _claimedArray[i]) ? (withheld !== '0' ? parseFloat(withheld) / parseFloat(amount) * 100 : 0) : 'NA';
let received = (!excluded && _claimedArray[i]) ? web3.utils.fromWei(web3.utils.toBN(_amountArray[i]).sub(web3.utils.toBN(_withheldArray[i]))) : 0;
let amount = !excluded ? web3.utils.fromWei(web3.utils.toBN(_amountArray[i]).add(web3.utils.toBN(_withheldArray[i]))) : 0;
let withheld = !excluded ? web3.utils.fromWei(_withheldArray[i]) : 'NA';
let withheldPercentage = (!excluded) ? (withheld !== '0' ? parseFloat(withheld) / parseFloat(amount) * 100 : 0) : 'NA';
let received = !excluded ? web3.utils.fromWei(_amountArray[i]) : 0;
dataTable.push([
investor,
amount,
Expand All @@ -476,6 +507,8 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo
console.log(`- Total amount of investors: ${_investorArray.length} `);
console.log();
console.log(table(dataTable));
console.log();
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.`));
console.log(chalk.yellow(`-----------------------------------------------------------------------------------------------------------------------------------------------------------`));
console.log();
}
Expand Down Expand Up @@ -561,7 +594,14 @@ async function addDividendsModule() {

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

let selectedDividendFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.DIVIDENDS, options[index]);
let addModuleAction = securityToken.methods.addModule(selectedDividendFactoryAddress, bytes, 0, 0);
Expand Down Expand Up @@ -655,11 +695,11 @@ async function selectDividend(dividends) {
let result = null;
let options = dividends.map(function (d) {
return `${d.name}
Amount: ${ web3.utils.fromWei(d.amount)} ${dividendsType}
Status: ${ isExpiredDividend(d) ? 'Expired' : hasRemaining(d) ? 'In progress' : 'Completed'}
Token: ${ d.tokenSymbol}
Created: ${ moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')}
Expiry: ${ moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} `
Amount: ${web3.utils.fromWei(d.amount)} ${d.tokenSymbol}
Status: ${isExpiredDividend(d) ? 'Expired' : hasRemaining(d) ? 'In progress' : 'Completed'}
Token: ${d.tokenSymbol}
Created: ${moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')}
Expiry: ${moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} `
});

let index = readlineSync.keyInSelect(options, 'Select a dividend:', { cancel: 'RETURN' });
Expand Down
47 changes: 39 additions & 8 deletions contracts/modules/Checkpoint/DividendCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp);
event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp);
event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp);
event SetWallet(address indexed _oldWallet, address indexed _newWallet, uint256 _timestamp);

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

/**
* @notice Function used to intialize the contract variables
* @param _wallet Ethereum account address to receive reclaimed dividends and tax
*/
function configure(
address _wallet
) public onlyFactory {
_setWallet(_wallet);
}

/**
* @notice Init function i.e generalise function to maintain the structure of the module contract
* @return bytes4
*/
function getInitFunction() public pure returns (bytes4) {
return bytes4(0);
return this.configure.selector;
}

/**
* @notice Function used to change wallet address
* @param _wallet Ethereum account address to receive reclaimed dividends and tax
*/
function changeWallet(address _wallet) external onlyOwner {
_setWallet(_wallet);
}

function _setWallet(address _wallet) internal {
require(_wallet != address(0));
emit SetWallet(wallet, _wallet, now);
wallet = _wallet;
}

/**
Expand Down Expand Up @@ -286,17 +311,17 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
* @return address[] list of investors
* @return bool[] whether investor has claimed
* @return bool[] whether investor is excluded
* @return uint256[] amount of withheld tax
* @return uint256[] amount of withheld tax (estimate if not claimed)
* @return uint256[] amount of claim (estimate if not claimeed)
* @return uint256[] investor balance
* @return uint256[] amount to be claimed including withheld tax
*/
function getDividendProgress(uint256 _dividendIndex) external view returns (
address[] memory investors,
bool[] memory resultClaimed,
bool[] memory resultExcluded,
uint256[] memory resultWithheld,
uint256[] memory resultBalance,
uint256[] memory resultAmount)
uint256[] memory resultAmount,
uint256[] memory resultBalance)
{
require(_dividendIndex < dividends.length, "Invalid dividend");
//Get list of Investors
Expand All @@ -306,15 +331,21 @@ contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module {
resultClaimed = new bool[](investors.length);
resultExcluded = new bool[](investors.length);
resultWithheld = new uint256[](investors.length);
resultBalance = new uint256[](investors.length);
resultAmount = new uint256[](investors.length);
resultBalance = new uint256[](investors.length);
for (uint256 i; i < investors.length; i++) {
resultClaimed[i] = dividend.claimed[investors[i]];
resultExcluded[i] = dividend.dividendExcluded[investors[i]];
resultBalance[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], dividend.checkpointId);
if (!resultExcluded[i]) {
resultWithheld[i] = dividend.withheld[investors[i]];
resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply);
if (resultClaimed[i]) {
resultWithheld[i] = dividend.withheld[investors[i]];
resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply).sub(resultWithheld[i]);
} else {
(uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, investors[i]);
resultWithheld[i] = withheld;
resultAmount[i] = claim.sub(withheld);
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions contracts/modules/Checkpoint/DividendCheckpointStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pragma solidity ^0.4.24;
*/
contract DividendCheckpointStorage {

// Address to which reclaimed dividends and withholding tax is sent
address public wallet;
uint256 public EXCLUDED_ADDRESS_LIMIT = 150;
bytes32 public constant DISTRIBUTE = "DISTRIBUTE";
bytes32 public constant MANAGE = "MANAGE";
Expand Down
10 changes: 4 additions & 6 deletions contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,8 @@ contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendChec
dividends[_dividendIndex].reclaimed = true;
Dividend storage dividend = dividends[_dividendIndex];
uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount);
address owner = IOwnable(securityToken).owner();
require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingAmount), "transfer failed");
emit ERC20DividendReclaimed(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount);
require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingAmount), "transfer failed");
emit ERC20DividendReclaimed(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount);
}

/**
Expand All @@ -275,9 +274,8 @@ contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendChec
Dividend storage dividend = dividends[_dividendIndex];
uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn);
dividend.totalWithheldWithdrawn = dividend.totalWithheld;
address owner = IOwnable(securityToken).owner();
require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingWithheld), "transfer failed");
emit ERC20DividendWithholdingWithdrawn(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld);
require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingWithheld), "transfer failed");
emit ERC20DividendWithholdingWithdrawn(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pragma solidity ^0.4.24;

import "../../proxy/ERC20DividendCheckpointProxy.sol";
import "../../libraries/Util.sol";
import "../../interfaces/IBoot.sol";
import "../ModuleFactory.sol";

/**
Expand Down Expand Up @@ -35,10 +37,14 @@ contract ERC20DividendCheckpointFactory is ModuleFactory {
* @notice Used to launch the Module with the help of factory
* @return Address Contract address of the Module
*/
function deploy(bytes /* _data */) external returns(address) {
function deploy(bytes _data) external returns(address) {
if (setupCost > 0)
require(polyToken.transferFrom(msg.sender, owner, setupCost), "insufficent allowance");
address erc20DividendCheckpoint = new ERC20DividendCheckpointProxy(msg.sender, address(polyToken), logicContract);
//Checks that _data is valid (not calling anything it shouldn't)
require(Util.getSig(_data) == IBoot(erc20DividendCheckpoint).getInitFunction(), "Invalid data");
/*solium-disable-next-line security/no-low-level-calls*/
require(erc20DividendCheckpoint.call(_data), "Unsuccessfull call");
/*solium-disable-next-line security/no-block-members*/
emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now);
return erc20DividendCheckpoint;
Expand Down
10 changes: 4 additions & 6 deletions contracts/modules/Checkpoint/EtherDividendCheckpoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,8 @@ contract EtherDividendCheckpoint is DividendCheckpoint {
Dividend storage dividend = dividends[_dividendIndex];
dividend.reclaimed = true;
uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount);
address owner = IOwnable(securityToken).owner();
owner.transfer(remainingAmount);
emit EtherDividendReclaimed(owner, _dividendIndex, remainingAmount);
wallet.transfer(remainingAmount);
emit EtherDividendReclaimed(wallet, _dividendIndex, remainingAmount);
}

/**
Expand All @@ -214,9 +213,8 @@ contract EtherDividendCheckpoint is DividendCheckpoint {
Dividend storage dividend = dividends[_dividendIndex];
uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn);
dividend.totalWithheldWithdrawn = dividend.totalWithheld;
address owner = IOwnable(securityToken).owner();
owner.transfer(remainingWithheld);
emit EtherDividendWithholdingWithdrawn(owner, _dividendIndex, remainingWithheld);
wallet.transfer(remainingWithheld);
emit EtherDividendWithholdingWithdrawn(wallet, _dividendIndex, remainingWithheld);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pragma solidity ^0.4.24;

import "../../proxy/EtherDividendCheckpointProxy.sol";
import "../../libraries/Util.sol";
import "../../interfaces/IBoot.sol";
import "../ModuleFactory.sol";

/**
Expand Down Expand Up @@ -35,10 +37,14 @@ contract EtherDividendCheckpointFactory is ModuleFactory {
* @notice Used to launch the Module with the help of factory
* @return address Contract address of the Module
*/
function deploy(bytes /* _data */) external returns(address) {
function deploy(bytes _data) external returns(address) {
if(setupCost > 0)
require(polyToken.transferFrom(msg.sender, owner, setupCost), "Insufficent allowance or balance");
address ethDividendCheckpoint = new EtherDividendCheckpointProxy(msg.sender, address(polyToken), logicContract);
//Checks that _data is valid (not calling anything it shouldn't)
require(Util.getSig(_data) == IBoot(ethDividendCheckpoint).getInitFunction(), "Invalid data");
/*solium-disable-next-line security/no-low-level-calls*/
require(ethDividendCheckpoint.call(_data), "Unsuccessfull call");
/*solium-disable-next-line security/no-block-members*/
emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now);
return ethDividendCheckpoint;
Expand Down
2 changes: 0 additions & 2 deletions contracts/modules/STO/USDTieredSTO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard {
address _reserveWallet,
address[] _usdTokens
) external onlyOwner {
/*solium-disable-next-line security/no-block-members*/
// require(now < startTime, "STO already started");
_modifyAddresses(_wallet, _reserveWallet, _usdTokens);
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/modules/STO/USDTieredSTOFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ contract USDTieredSTOFactory is ModuleFactory {
//Checks that _data is valid (not calling anything it shouldn't)
require(Util.getSig(_data) == IBoot(usdTieredSTO).getInitFunction(), "Invalid data");
/*solium-disable-next-line security/no-low-level-calls*/
require(address(usdTieredSTO).call(_data), "Unsuccessfull call");
require(usdTieredSTO.call(_data), "Unsuccessfull call");
/*solium-disable-next-line security/no-block-members*/
emit GenerateModuleFromFactory(usdTieredSTO, getName(), address(this), msg.sender, setupCost, now);
return address(usdTieredSTO);
return usdTieredSTO;
}

/**
Expand Down
Loading