From 954c2c2be317ff8f2a2869e04ef2e20914f0c186 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 15 Jan 2019 10:15:52 -0400 Subject: [PATCH 1/6] Working --- .../modules/Checkpoint/DividendCheckpoint.sol | 27 ++++++++++++++++- .../Checkpoint/DividendCheckpointStorage.sol | 2 ++ .../Checkpoint/ERC20DividendCheckpoint.sol | 10 +++---- .../ERC20DividendCheckpointFactory.sol | 8 ++++- .../Checkpoint/EtherDividendCheckpoint.sol | 10 +++---- .../EtherDividendCheckpointFactory.sol | 8 ++++- contracts/modules/STO/USDTieredSTO.sol | 2 -- contracts/modules/STO/USDTieredSTOFactory.sol | 4 +-- test/e_erc20_dividends.js | 29 ++++++++++--------- 9 files changed, 68 insertions(+), 32 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index 8f8c75e2d..80e1c5c31 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -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"); @@ -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; } /** diff --git a/contracts/modules/Checkpoint/DividendCheckpointStorage.sol b/contracts/modules/Checkpoint/DividendCheckpointStorage.sol index 85e7e789a..2ae0473ac 100644 --- a/contracts/modules/Checkpoint/DividendCheckpointStorage.sol +++ b/contracts/modules/Checkpoint/DividendCheckpointStorage.sol @@ -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"; diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 19be8a606..1d3c8b1bb 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -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); } /** @@ -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); } } diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol index bae9913b8..3935154cf 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol @@ -1,6 +1,8 @@ pragma solidity ^0.4.24; import "../../proxy/ERC20DividendCheckpointProxy.sol"; +import "../../libraries/Util.sol"; +import "../../interfaces/IBoot.sol"; import "../ModuleFactory.sol"; /** @@ -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; diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 81d94d3ce..2952fc955 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -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); } /** @@ -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); } } diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol index 712292d0a..923af1c64 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol @@ -1,6 +1,8 @@ pragma solidity ^0.4.24; import "../../proxy/EtherDividendCheckpointProxy.sol"; +import "../../libraries/Util.sol"; +import "../../interfaces/IBoot.sol"; import "../ModuleFactory.sol"; /** @@ -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; diff --git a/contracts/modules/STO/USDTieredSTO.sol b/contracts/modules/STO/USDTieredSTO.sol index 97345ad51..f1c959671 100644 --- a/contracts/modules/STO/USDTieredSTO.sol +++ b/contracts/modules/STO/USDTieredSTO.sol @@ -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); } diff --git a/contracts/modules/STO/USDTieredSTOFactory.sol b/contracts/modules/STO/USDTieredSTOFactory.sol index 3263a4fa7..1e44c7a7e 100644 --- a/contracts/modules/STO/USDTieredSTOFactory.sol +++ b/contracts/modules/STO/USDTieredSTOFactory.sol @@ -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; } /** diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index a13047fc0..c300d8996 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -3,6 +3,7 @@ import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployERC20DividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); @@ -18,6 +19,7 @@ contract("ERC20DividendCheckpoint", accounts => { let account_polymath; let account_issuer; let token_owner; + let wallet; let account_investor1; let account_investor2; let account_investor3; @@ -76,6 +78,8 @@ contract("ERC20DividendCheckpoint", accounts => { const one_address = '0x0000000000000000000000000000000000000001'; + const DividendParameters = ["address"]; + before(async () => { // Accounts setup account_polymath = accounts[0]; @@ -89,6 +93,7 @@ contract("ERC20DividendCheckpoint", accounts => { account_investor4 = accounts[9]; account_temp = accounts[2]; account_manager = accounts[5]; + wallet = accounts[3]; // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -157,8 +162,9 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should successfully attach the ERC20DividendCheckpoint with the security token - fail insufficient payment", async () => { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ); @@ -168,7 +174,8 @@ contract("ERC20DividendCheckpoint", accounts => { let snapId = await takeSnapshot(); await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); @@ -182,7 +189,8 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, "", 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -864,9 +872,9 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(info[5][2].toNumber(), web3.utils.toWei("1", "ether"), "claim match"); assert.equal(info[5][3].toNumber(), web3.utils.toWei("7", "ether"), "claim match"); - let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.withdrawWithholding(3, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let issuerBalanceAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.4", "ether")); }); @@ -884,9 +892,9 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let tokenOwnerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let tokenOwnerAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); }); @@ -908,11 +916,6 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(index.length, 0); }); - it("Get the init data", async () => { - let tx = await I_ERC20DividendCheckpoint.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); - }); - it("Should get the listed permissions", async () => { let tx = await I_ERC20DividendCheckpoint.getPermissions.call(); assert.equal(tx.length, 2); @@ -1098,7 +1101,7 @@ contract("ERC20DividendCheckpoint", accounts => { { from: account_manager } ); let info = await I_ERC20DividendCheckpoint.getCheckpointData.call(checkpointID); - + assert.equal(info[0][0], account_investor1, "account match"); assert.equal(info[0][1], account_investor2, "account match"); assert.equal(info[0][2], account_temp, "account match"); From a9c91fdcd5d90bac862c706eb31067f55cad5f57 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 15 Jan 2019 10:20:58 -0400 Subject: [PATCH 2/6] Wallet change test case --- test/e_erc20_dividends.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index c300d8996..bc72a7ef6 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -878,6 +878,16 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.4", "ether")); }); + it("Issuer changes wallet address", async () => { + await catchRevert(I_ERC20DividendCheckpoint.changeWallet(token_owner, { from: wallet })); + await I_ERC20DividendCheckpoint.changeWallet(token_owner, {from: token_owner}); + let newWallet = await I_ERC20DividendCheckpoint.wallet.call(); + assert.equal(newWallet, token_owner, "Wallets match"); + await I_ERC20DividendCheckpoint.changeWallet(wallet, {from: token_owner}); + newWallet = await I_ERC20DividendCheckpoint.wallet.call(); + assert.equal(newWallet, wallet, "Wallets match"); + }); + it("Issuer unable to reclaim dividend (expiry not passed)", async () => { await catchRevert(I_ERC20DividendCheckpoint.reclaimDividend(3, { from: token_owner })); }); From ede652c94d6fe0e71e82d03232cb2db3a8ee88e6 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 15 Jan 2019 10:41:58 -0400 Subject: [PATCH 3/6] Fix ether dividend tests --- test/f_ether_dividends.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 98fd092cc..6849e9cbd 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -4,6 +4,7 @@ import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployEtherDividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); @@ -19,6 +20,7 @@ contract("EtherDividendCheckpoint", accounts => { let account_polymath; let account_issuer; let token_owner; + let wallet; let account_investor1; let account_investor2; let account_investor3; @@ -68,6 +70,7 @@ contract("EtherDividendCheckpoint", accounts => { const transferManagerKey = 2; const stoKey = 3; const checkpointKey = 4; + const DividendParameters = ["address"]; // Initial fee for ticker registry and security token registry const initRegFee = web3.utils.toWei("250"); @@ -85,6 +88,7 @@ contract("EtherDividendCheckpoint", accounts => { account_investor4 = accounts[9]; account_manager = accounts[5]; account_temp = accounts[2]; + wallet = accounts[3]; // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -156,8 +160,9 @@ contract("EtherDividendCheckpoint", accounts => { it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ); @@ -166,7 +171,8 @@ contract("EtherDividendCheckpoint", accounts => { it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { let snapId = await takeSnapshot(); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); @@ -180,7 +186,8 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, "", 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -343,9 +350,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer reclaims withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.2", "ether")); }); @@ -491,9 +498,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer withdraws new withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(2, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.6", "ether")); }); @@ -699,9 +706,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let tokenOwnerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await web3.eth.getBalance(token_owner)); + let tokenOwnerAfter = new BigNumber(await web3.eth.getBalance(wallet)); assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); }); @@ -781,11 +788,6 @@ contract("EtherDividendCheckpoint", accounts => { assert.equal(index.length, 0); }); - it("Get the init data", async () => { - let tx = await I_EtherDividendCheckpoint.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); - }); - it("Should get the listed permissions", async () => { let tx = await I_EtherDividendCheckpoint.getPermissions.call(); assert.equal(tx.length, 2); From 5f6436765efc98ec3075631c03551fac76b41a1f Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 15 Jan 2019 11:44:51 -0300 Subject: [PATCH 4/6] CLI - Update for wallet on dividends modules --- CLI/commands/dividends_manager.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index 15b624953..a722d1087 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -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'); } @@ -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; @@ -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); @@ -561,7 +579,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); From 383643e581ed6f32339ec222a453ebb103183475 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 15 Jan 2019 11:24:14 -0400 Subject: [PATCH 5/6] Add estimated tax / amounts to getter --- .../modules/Checkpoint/DividendCheckpoint.sol | 20 ++++--- test/e_erc20_dividends.js | 56 ++++++++++++++++--- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index 80e1c5c31..30cf49c54 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -311,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 @@ -331,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); + } } } } diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index bc72a7ef6..1d3820991 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -852,25 +852,63 @@ contract("ERC20DividendCheckpoint", accounts => { it("Issuer reclaims withholding tax", async () => { let info = await I_ERC20DividendCheckpoint.getDividendProgress.call(3); - console.log(info); + + console.log("Address:"); + console.log(info[0][0]); + console.log(info[0][1]); + console.log(info[0][2]); + console.log(info[0][3]); + + console.log("Claimed:"); + console.log(info[1][0]); + console.log(info[1][1]); + console.log(info[1][2]); + console.log(info[1][3]); + + console.log("Excluded:"); + console.log(info[2][0]); + console.log(info[2][1]); + console.log(info[2][2]); + console.log(info[2][3]); + + console.log("Withheld:"); + console.log(info[3][0].toNumber()); + console.log(info[3][1].toNumber()); + console.log(info[3][2].toNumber()); + console.log(info[3][3].toNumber()); + + console.log("Claimed:"); + console.log(info[4][0].toNumber()); + console.log(info[4][1].toNumber()); + console.log(info[4][2].toNumber()); + console.log(info[4][3].toNumber()); + + console.log("Balance:"); + console.log(info[5][0].toNumber()); + console.log(info[5][1].toNumber()); + console.log(info[5][2].toNumber()); + console.log(info[5][3].toNumber()); + assert.equal(info[0][0], account_investor1, "account match"); assert.equal(info[0][1], account_investor2, "account match"); assert.equal(info[0][2], account_temp, "account match"); assert.equal(info[0][3], account_investor3, "account match"); + assert.equal(info[3][0].toNumber(), 0, "withheld match"); assert.equal(info[3][1].toNumber(), web3.utils.toWei("0.2", "ether"), "withheld match"); assert.equal(info[3][2].toNumber(), web3.utils.toWei("0.2", "ether"), "withheld match"); assert.equal(info[3][3].toNumber(), 0, "withheld match"); - assert.equal(info[4][0].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), "balance match"); - assert.equal(info[4][1].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), "balance match"); - assert.equal(info[4][2].toNumber(), (await I_SecurityToken.balanceOfAt(account_temp, 4)).toNumber(), "balance match"); - assert.equal(info[4][3].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), "balance match"); + assert.equal(info[4][0].toNumber(), 0, "excluded"); + assert.equal(info[4][1].toNumber(), web3.utils.toWei("1.8", "ether"), "claim match"); + assert.equal(info[4][2].toNumber(), web3.utils.toWei("0.8", "ether"), "claim match"); + assert.equal(info[4][3].toNumber(), web3.utils.toWei("7", "ether"), "claim match"); + + assert.equal(info[5][0].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), "balance match"); + assert.equal(info[5][1].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), "balance match"); + assert.equal(info[5][2].toNumber(), (await I_SecurityToken.balanceOfAt(account_temp, 4)).toNumber(), "balance match"); + assert.equal(info[5][3].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), "balance match"); - assert.equal(info[5][0].toNumber(), 0, "excluded"); - assert.equal(info[5][1].toNumber(), web3.utils.toWei("2", "ether"), "claim match"); - assert.equal(info[5][2].toNumber(), web3.utils.toWei("1", "ether"), "claim match"); - assert.equal(info[5][3].toNumber(), web3.utils.toWei("7", "ether"), "claim match"); let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.withdrawWithholding(3, { from: token_owner, gasPrice: 0 }); From 4f4a8213cc53965a9cefb40e0d7d870e7be9f116 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 15 Jan 2019 14:49:37 -0300 Subject: [PATCH 6/6] CLI - Update for dividends status data --- CLI/commands/dividends_manager.js | 45 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index a722d1087..0b4dd8f39 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -248,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')}`); @@ -258,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} `); // ------------------ @@ -472,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, @@ -494,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(); } @@ -680,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' });