From 35c85d5aa3601dcbca92eb7a61258dd83f845dcf Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 26 Jun 2018 11:45:47 +0100 Subject: [PATCH 01/22] WIP --- .../EtherDividendWithholdingCheckpoint.sol | 109 +++ ...erDividendWithholdingCheckpointFactory.sol | 76 ++ test/ether_withholding_dividends.js | 856 ++++++++++++++++++ 3 files changed, 1041 insertions(+) create mode 100644 contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol create mode 100644 contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol create mode 100644 test/ether_withholding_dividends.js diff --git a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol new file mode 100644 index 000000000..2dd9d28f5 --- /dev/null +++ b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol @@ -0,0 +1,109 @@ +pragma solidity ^0.4.24; + +import "./EtherDividendCheckpoint.sol"; + +/** + * @title Checkpoint module for issuing ether dividends + */ +contract EtherDividendWithholdingCheckpoint is EtherDividendCheckpoint { + using SafeMath for uint256; + + // Mapping from address to withholding tax as a percentage * 10**16 + mapping (address => uint256) public withholdingTax; + + uint256 public totalWithheld; + mapping (uint256 => uint256) public dividendWithheld; + mapping (uint256 => uint256) public dividendWithheldReclaimed; + mapping (address => uint256) public investorWithheld; + + event EtherDividendWithholdingClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) public + EtherDividendCheckpoint(_securityToken, _polyAddress) + { + } + + /** + * @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); + for (uint256 i = 0; i < _investors.length; i++) { + withholdingTax[_investors[i]] = _withholding[i]; + } + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors addresses of investor + * @param _withholding withholding tax for all investors (multiplied by 10**16) + */ + function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { + for (uint256 i = 0; i < _investors.length; i++) { + withholdingTax[_investors[i]] = _withholding; + } + } + + /** + * @notice Internal function for paying dividends + * @param _payee address of investor + * @param _dividend storage with previously issued dividends + * @param _dividendIndex Dividend to pay + */ + function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { + (uint256 claim, uint256 withheld) = calculateDividendWithholding(_dividendIndex, _payee); + _dividend.claimed[_payee] = true; + _dividend.claimedAmount = claim.add(_dividend.claimedAmount); + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + if (_payee.send(claimAfterWithheld)) { + totalWithheld = totalWithheld.add(withheld); + dividendWithheld[_dividendIndex] = dividendWithheld[_dividendIndex].add(withheld); + investorWithheld[_payee] = investorWithheld[_payee].add(withheld); + emit EtherDividendWithholdingClaimed(_payee, _dividendIndex, claim, withheld); + } else { + _dividend.claimed[_payee] = false; + emit EtherDividendWithholdingClaimFailed(_payee, _dividendIndex, claim, withheld); + } + } + } + + /** + * @notice Calculate amount of dividends claimable + * @param _dividendIndex Dividend to calculate + * @param _payee Affected investor address + * @return full claim + * @return amount to be withheld + */ + function calculateDividendWithholding(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee]) { + return (0, 0); + } + uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(uint256(10**18).sub(withholdingTax[_payee])).div(uint256(10**18)); + return (claim, withheld); + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) public onlyOwner { + uint256 remainingWithheld = dividendWithheld[_dividendIndex].sub(dividendWithheldReclaimed[_dividendIndex]); + dividendWithheldReclaimed[_dividendIndex] = dividendWithheld[_dividendIndex]; + msg.sender.transfer(remainingWithheld); + emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); + } + +} diff --git a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol new file mode 100644 index 000000000..2b5115df9 --- /dev/null +++ b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol @@ -0,0 +1,76 @@ +pragma solidity ^0.4.24; + +import "./EtherDividendWithholdingCheckpoint.sol"; +import "../../interfaces/IModuleFactory.sol"; + +/** + * @title Factory for deploying EtherDividendCheckpoint module + */ +contract EtherDividendWithholdingCheckpointFactory is IModuleFactory { + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _usageCost Usage cost of the module + * @param _subscriptionCost Subscription cost of the module + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + IModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + + } + + /** + * @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) { + if(setupCost > 0) + require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); + return address(new EtherDividendWithholdingCheckpoint(msg.sender, address(polyToken))); + } + + /** + * @notice Type of the Module factory + */ + function getType() public view returns(uint8) { + return 4; + } + + /** + * @notice Get the name of the Module + */ + function getName() public view returns(bytes32) { + return bytes32("EthDividendWithholdingCheckpoint"); + } + + /** + * @notice Get the description of the Module + */ + function getDescription() public view returns(string) { + return "Create ETH dividends for token holders at a specific checkpoint, and allow tax to be withheld"; + } + + /** + * @notice Get the title of the Module + */ + function getTitle() public view returns(string) { + return "Ether Dividend Withholding Checkpoint"; + } + + /** + * @notice Get the Instructions that helped to used the module + */ + function getInstructions() public view returns(string) { + return "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created. Withholding tax is specified per investor (defaults to zero) and withheld from dividends"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](0); + return availableTags; + } +} diff --git a/test/ether_withholding_dividends.js b/test/ether_withholding_dividends.js new file mode 100644 index 000000000..3c476425f --- /dev/null +++ b/test/ether_withholding_dividends.js @@ -0,0 +1,856 @@ +import latestTime from './helpers/latestTime'; +import { duration, ensureException } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { error } from 'util'; + +const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); +const TickerRegistry = artifacts.require('./TickerRegistry.sol'); +const STVersion = artifacts.require('./STVersionProxy001.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const EtherDividendWithholdingCheckpointFactory = artifacts.require('./EtherDividendWithholdingCheckpointFactory.sol'); +const EtherDividendWithholdingCheckpoint = artifacts.require('./EtherDividendWithholdingCheckpoint'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const PolyTokenFaucet = artifacts.require('./PolyTokenFaucet.sol'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('EtherDividendWithholdingCheckpoint', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_temp; + + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_GeneralTransferManagerFactory; + let P_EtherDividendWithholdingCheckpointFactory; + let P_EtherDividendWithholdingCheckpoint; + let I_EtherDividendWithholdingCheckpointFactory; + let I_GeneralPermissionManager; + let I_EtherDividendWithholdingCheckpoint; + let I_GeneralTransferManager; + let I_ExchangeTransferManager; + let I_ModuleRegistry; + let I_TickerRegistry; + let I_SecurityTokenRegistry; + let I_STVersion; + let I_SecurityToken; + let I_PolyToken; + + // SecurityToken Details + const swarmHash = "dagwrgwgvwergwrvwrg"; + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + let snapId; + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + const checkpointKey = 4; + + // Initial fee for ticker registry and security token registry + const initRegFee = 250 * Math.pow(10, 18); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[6]; + account_investor2 = accounts[7]; + account_investor3 = accounts[8]; + account_investor4 = accounts[9]; + account_temp = accounts[2]; + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 0: Deploy the token Faucet and Mint tokens for token_owner + I_PolyToken = await PolyTokenFaucet.new(); + await I_PolyToken.getTokens((10000 * Math.pow(10, 18)), token_owner); + + // STEP 1: Deploy the ModuleRegistry + + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); + + assert.notEqual( + I_ModuleRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "ModuleRegistry contract was not deployed" + ); + + // STEP 2: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); + + // STEP 3: Deploy the GeneralDelegateManagerFactory + + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralPermissionManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManagerFactory contract was not deployed" + ); + + // STEP 4: Deploy the EtherDividendWithholdingCheckpoint + I_EtherDividendWithholdingCheckpointFactory = await EtherDividendWithholdingCheckpointFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + assert.notEqual( + I_EtherDividendWithholdingCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "EtherDividendWithholdingCheckpointFactory contract was not deployed" + ); + + // STEP 5: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_ModuleRegistry.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_ModuleRegistry.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (C) : Register the EtherDividendWithholdingCheckpointFactory + await I_ModuleRegistry.registerModule(I_EtherDividendWithholdingCheckpointFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_EtherDividendWithholdingCheckpointFactory.address, true, { from: account_polymath }); + + // (C) : Register the Paid EtherDividendWithholdingCheckpointFactory + await I_ModuleRegistry.registerModule(P_EtherDividendWithholdingCheckpointFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(P_EtherDividendWithholdingCheckpointFactory.address, true, { from: account_polymath }); + + // Step 6: Deploy the TickerRegistry + + I_TickerRegistry = await TickerRegistry.new(I_PolyToken.address, initRegFee, { from: account_polymath }); + + assert.notEqual( + I_TickerRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "TickerRegistry contract was not deployed", + ); + + // Step 7: Deploy the STversionProxy contract + + I_STVersion = await STVersion.new(I_GeneralTransferManagerFactory.address); + + assert.notEqual( + I_STVersion.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STVersion contract was not deployed", + ); + + // Step 8: Deploy the SecurityTokenRegistry + + I_SecurityTokenRegistry = await SecurityTokenRegistry.new( + I_PolyToken.address, + I_ModuleRegistry.address, + I_TickerRegistry.address, + I_STVersion.address, + initRegFee, + { + from: account_polymath + }); + + assert.notEqual( + I_SecurityTokenRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SecurityTokenRegistry contract was not deployed", + ); + + // Step 8: Set the STR in TickerRegistry + await I_TickerRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistry.address, {from: account_polymath}); + await I_ModuleRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistry.address, {from: account_polymath}); + + // Printing all the contract addresses + console.log(`\nPolymath Network Smart Contracts Deployed:\n + ModuleRegistry: ${I_ModuleRegistry.address}\n + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address}\n + EtherDividendWithholdingCheckpointFactory: ${I_EtherDividendWithholdingCheckpointFactory.address}\n + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address}\n + TickerRegistry: ${I_TickerRegistry.address}\n + STVersionProxy_001: ${I_STVersion.address}\n + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address}\n + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_TickerRegistry.address, initRegFee, { from: token_owner }); + let tx = await I_TickerRegistry.registerTicker(token_owner, symbol, contact, swarmHash, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._symbol, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_SecurityTokenRegistry.address, initRegFee, { from: token_owner }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner, gas: 85000000 }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const LogAddModule = await I_SecurityToken.allEvents(); + const log = await new Promise(function(resolve, reject) { + LogAddModule.watch(function(error, log){ resolve(log);}); + }); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._type.toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + LogAddModule.stopWatching(); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = await I_SecurityToken.modules(2, 0); + I_GeneralTransferManager = GeneralTransferManager.at(moduleData[1]); + + assert.notEqual( + I_GeneralTransferManager.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManager contract was not deployed", + ); + + }); + + it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { + let errorThrown = false; + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + try { + const tx = await I_SecurityToken.addModule(P_EtherDividendWithholdingCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, true, { from: token_owner }); + } catch(error) { + console.log(` tx -> failed because Token is not paid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should successfully attach the EtherDividendWithholdingCheckpoint 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_EtherDividendWithholdingCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, true, { from: token_owner }); + assert.equal(tx.logs[3].args._type.toNumber(), checkpointKey, "EtherDividendWithholdingCheckpoint doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[3].args._name) + .replace(/\u0000/g, ''), + "EtherDividendWithholdingCheckpoint", + "EtherDividendWithholdingCheckpoint module was not added" + ); + P_EtherDividendWithholdingCheckpoint = EtherDividendWithholdingCheckpoint.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + + it("Should successfully attach the EtherDividendWithholdingCheckpoint with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_EtherDividendWithholdingCheckpointFactory.address, "", 0, 0, true, { from: token_owner }); + assert.equal(tx.logs[2].args._type.toNumber(), checkpointKey, "EtherDividendWithholdingCheckpoint doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "EtherDividendWithholdingCheckpoint", + "EtherDividendWithholdingCheckpoint module was not added" + ); + I_EtherDividendWithholdingCheckpoint = EtherDividendWithholdingCheckpoint.at(tx.logs[2].args._module); + }); + }); + + describe("Check Dividend payouts", async() => { + + it("Buy some tokens for account_investor1 (1 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("Buy some tokens for account_investor2 (2 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should fail in creating the dividend", async() => { + let errorThrown = false; + let maturity = latestTime(); + let expiry = latestTime() + duration.days(10); + try { + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because msg.value = 0`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail in creating the dividend", async() => { + let errorThrown = false; + let maturity = latestTime(); + let expiry = latestTime() - duration.days(10); + try { + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + } catch(error) { + console.log(` tx -> failed because maturity > expiry`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail in creating the dividend", async() => { + let errorThrown = false; + let maturity = latestTime() - duration.days(2); + let expiry = latestTime() - duration.days(1); + try { + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + } catch(error) { + console.log(` tx -> failed because now > expiry`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create new dividend", async() => { + let maturity = latestTime() + duration.days(1); + let expiry = latestTime() + duration.days(10); + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); + }); + + it("Investor 1 transfers his token balance to investor 2", async() => { + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), {from: account_investor1}); + assert.equal(await I_SecurityToken.balanceOf(account_investor1), 0); + assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('3', 'ether')); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because dividend index has maturity in future`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let errorThrown = false; + // Increase time by 2 day + await increaseTime(duration.days(2)); + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: account_temp}); + } catch(error) { + console.log(` tx -> failed because msg.sender is not the owner`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because dividend index is greator than the dividend array length`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); + let investor1BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor2)); + assert.equal(investor1BalanceAfter.sub(investor1Balance).toNumber(), web3.utils.toWei('0.5', 'ether')); + assert.equal(investor2BalanceAfter.sub(investor2Balance).toNumber(), web3.utils.toWei('1', 'ether')); + //Check fully claimed + assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei('1.5', 'ether')); + }); + + it("Buy some tokens for account_temp (1 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_temp, + latestTime(), + latestTime(), + latestTime() + duration.days(20), + true, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_temp.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_temp, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_temp)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("Create new dividend", async() => { + let maturity = latestTime() + duration.days(1); + let expiry = latestTime() + duration.days(10); + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let errorThrown = false; + await increaseTime(duration.days(12)); + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because dividend index has passed its expiry`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + let errorThrown = false; + let tx = await I_EtherDividendWithholdingCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); + assert.equal((tx.logs[0].args._claimedAmount).toNumber(), web3.utils.toWei("1.5", "ether")); + try { + await I_EtherDividendWithholdingCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); + } catch(error) { + console.log(` tx -> failed because dividend index has already reclaimed`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Buy some tokens for account_investor3 (7 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor3, web3.utils.toWei('7', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('7', 'ether') + ); + }); + + it("Create another new dividend", async() => { + let maturity = latestTime(); + let expiry = latestTime() + duration.days(10); + let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); + }); + + it("should investor 3 claims dividend", async() => { + let errorThrown = false; + let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); + try { + await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(5, {from: account_investor3, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because dividend index is not valid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should investor 3 claims dividend", async() => { + let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3Balance = BigNumber(await web3.eth.getBalance(account_investor3)); + await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(2, {from: account_investor3, gasPrice: 0}); + let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); + assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); + assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), web3.utils.toWei('7', 'ether')); + }); + + it("should investor 3 claims dividend", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(2, {from: account_investor3, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because investor already claimed the dividend`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer pushes remainder", async() => { + let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); + await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); + let investor1BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); + assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); + assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('3', 'ether')); + assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); + //Check fully claimed + assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); + }); + + it("Investor 2 transfers 1 ETH of his token balance to investor 1", async() => { + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), {from: account_investor2}); + assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('2', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); + }); + + it("Create another new dividend with explicit", async() => { + let errorThrown = false; + let maturity = latestTime(); + let expiry = latestTime() + duration.days(2); + let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); + try { + tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: 0}); + } catch(error) { + console.log(` tx -> failed because msg.value is 0`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create another new dividend with explicit", async() => { + let errorThrown = false; + let maturity = latestTime(); + let expiry = latestTime() - duration.days(10); + try { + tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + } catch(error) { + console.log(` tx -> failed because maturity > expiry`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create another new dividend with explicit", async() => { + let errorThrown = false; + let maturity = latestTime() - duration.days(5); + let expiry = latestTime() - duration.days(2); + try { + tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + } catch(error) { + console.log(` tx -> failed because now > expiry`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create another new dividend with explicit", async() => { + let errorThrown = false; + let maturity = latestTime(); + let expiry = latestTime() + duration.days(2); + try { + tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + } catch(error) { + console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create another new dividend with explicit", async() => { + let maturity = latestTime(); + let expiry = latestTime() + duration.days(10); + let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); + tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); + }); + + it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + let errorThrown = false; + let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(4, [account_investor2, account_investor1],{from: account_investor2, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because not called by the owner`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + let errorThrown = false; + let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); + let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); + let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); + try { + await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(6, [account_investor2, account_investor1],{from: token_owner, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because dividend index is not valid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("should calculate dividend before the push dividend payment", async() => { + let dividendAmount1 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor1); + let dividendAmount2 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor2); + let dividendAmount3 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor3); + let dividendAmount_temp = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_temp); + assert.equal(dividendAmount1.toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount2.toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(dividendAmount3.toNumber(), web3.utils.toWei("7", "ether")); + assert.equal(dividendAmount_temp.toNumber(), web3.utils.toWei("1", "ether")); + }); + + + it("Investor 2 claims dividend", async() => { + let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3Balance = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalance = BigNumber(await web3.eth.getBalance(account_temp)); + await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(3, {from: account_investor2, gasPrice: 0}); + let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); + assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('2', 'ether')); + assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); + assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); + + }); + + + it("Should issuer pushes investor 1 and temp investor", async() => { + let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); + await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(3, [account_investor1, account_temp], {from: token_owner}); + let investor1BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter2 = BigNumber(await web3.eth.getBalance(account_temp)); + assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); + assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); + assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + //Check fully claimed + assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); + }); + + it("should calculate dividend after the push dividend payment", async() => { + let dividendAmount1 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor1); + let dividendAmount2 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor2); + assert.equal(dividendAmount1.toNumber(), 0); + assert.equal(dividendAmount2.toNumber(), 0); + }); + + it("Issuer unable to reclaim dividend (expiry not passed)", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner}); + } catch(error) { + console.log(`Tx Failed because expiry is in the future ${0}. Test Passed Successfully`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + it("Issuer is able to reclaim dividend after expiry", async() => { + let errorThrown = false; + await increaseTime(11 * 24 * 60 * 60); + try { + await I_EtherDividendWithholdingCheckpoint.reclaimDividend(8, {from: token_owner, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because dividend index is not valid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Issuer is able to reclaim dividend after expiry", async() => { + let tokenOwnerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner, gasPrice: 0}); + let tokenOwnerAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei('7', 'ether')); + }); + + it("Issuer is able to reclaim dividend after expiry", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner, gasPrice: 0}); + } catch(error) { + console.log(` tx -> failed because dividend are already reclaimed`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Investor 3 unable to pull dividend after expiry", async() => { + let errorThrown = false; + try { + await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(3, {from: account_investor3, gasPrice: 0}); + } catch(error) { + console.log(`Tx Failed because expiry is in the past ${0}. Test Passed Successfully`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + + }); + + it("Should give the right dividend index", async() => { + let index = await I_EtherDividendWithholdingCheckpoint.getDividendIndex.call(3); + assert.equal(index[0], 2); + }); + + it("Should give the right dividend index", async() => { + let index = await I_EtherDividendWithholdingCheckpoint.getDividendIndex.call(8); + assert.equal(index.length, 0); + }); + + it("Get the init data", async() => { + let tx = await I_EtherDividendWithholdingCheckpoint.getInitFunction.call(); + assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ''),0); + }); + + it("Should get the listed permissions", async() => { + let tx = await I_EtherDividendWithholdingCheckpoint.getPermissions.call(); + assert.equal(tx.length,0); + }); + + describe("Test cases for the EtherDividendWithholdingCheckpointFactory", async() => { + it("should get the exact details of the factory", async() => { + assert.equal((await I_EtherDividendWithholdingCheckpointFactory.setupCost.call()).toNumber(), 0); + assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getType.call(), 4); + assert.equal(web3.utils.toAscii(await I_EtherDividendWithholdingCheckpointFactory.getName.call()) + .replace(/\u0000/g, ''), + "EtherDividendWithholdingCheckpoint", + "Wrong Module added"); + assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getDescription.call(), + "Create ETH dividends for token holders at a specific checkpoint", + "Wrong Module added"); + assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getTitle.call(), + "Ether Dividend Checkpoint", + "Wrong Module added"); + assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getInstructions.call(), + "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created", + "Wrong Module added"); + let tags = await I_EtherDividendWithholdingCheckpointFactory.getTags.call(); + assert.equal(tags.length, 0); + + }); + }); + + }); + +}); From 7b4853791f782500b3c6c7537afdd197be78b095 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 26 Jun 2018 19:43:04 +0100 Subject: [PATCH 02/22] Add withholding tax to ether dividends --- .../Checkpoint/EtherDividendCheckpoint.sol | 80 +- .../EtherDividendWithholdingCheckpoint.sol | 109 --- ...erDividendWithholdingCheckpointFactory.sol | 76 -- test/ether_dividends.js | 55 +- test/ether_withholding_dividends.js | 856 ------------------ 5 files changed, 100 insertions(+), 1076 deletions(-) delete mode 100644 contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol delete mode 100644 contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol delete mode 100644 test/ether_withholding_dividends.js diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 09862725d..7ea63e305 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -26,10 +26,21 @@ contract EtherDividendCheckpoint is ICheckpoint { // List of all dividends Dividend[] public dividends; + // Mapping from address to withholding tax as a percentage * 10**16 + mapping (address => uint256) public withholdingTax; + + // Total amount of ETH withheld per dividend + mapping (uint256 => uint256) public dividendWithheld; + // Total amount of withheld ETH withdrawn by issuer per dividend + mapping (uint256 => uint256) public dividendWithheldReclaimed; + // Total amount of ETH withheld per investor + mapping (address => uint256) public investorWithheld; + event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); - event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount); + event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); - event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount); + event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); modifier validDividendIndex(uint256 _dividendIndex) { require(_dividendIndex < dividends.length, "Incorrect dividend index"); @@ -57,6 +68,31 @@ contract EtherDividendCheckpoint is ICheckpoint { return bytes4(0); } + /** + * @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); + for (uint256 i = 0; i < _investors.length; i++) { + require(_withholding[i] <= 10**18); + withholdingTax[_investors[i]] = _withholding[i]; + } + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors addresses of investor + * @param _withholding withholding tax for all investors (multiplied by 10**16) + */ + function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { + require(_withholding <= 10**18); + for (uint256 i = 0; i < _investors.length; i++) { + withholdingTax[_investors[i]] = _withholding; + } + } + /** * @notice Creates a dividend and checkpoint for the dividend * @param _maturity Time from which dividend can be paid @@ -161,15 +197,18 @@ contract EtherDividendCheckpoint is ICheckpoint { * @param _dividendIndex Dividend to pay */ function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { - uint256 claim = calculateDividend(_dividendIndex, _payee); + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); _dividend.claimed[_payee] = true; _dividend.claimedAmount = claim.add(_dividend.claimedAmount); - if (claim > 0) { - if (_payee.send(claim)) { - emit EtherDividendClaimed(_payee, _dividendIndex, claim); + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + if (_payee.send(claimAfterWithheld)) { + dividendWithheld[_dividendIndex] = dividendWithheld[_dividendIndex].add(withheld); + investorWithheld[_payee] = investorWithheld[_payee].add(withheld); + emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); } else { _dividend.claimed[_payee] = false; - emit EtherDividendClaimFailed(_payee, _dividendIndex, claim); + emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); } } } @@ -195,13 +234,15 @@ contract EtherDividendCheckpoint is ICheckpoint { * @param _payee Affected investor address * @return unit256 */ - function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256) { - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee]) { - return 0; - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); - return balance.mul(dividend.amount).div(dividend.totalSupply); + function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee]) { + return (0, 0); + } + uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); + return (claim, withheld); } /** @@ -228,6 +269,17 @@ contract EtherDividendCheckpoint is ICheckpoint { return index; } + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) public onlyOwner { + uint256 remainingWithheld = dividendWithheld[_dividendIndex].sub(dividendWithheldReclaimed[_dividendIndex]); + dividendWithheldReclaimed[_dividendIndex] = dividendWithheld[_dividendIndex]; + msg.sender.transfer(remainingWithheld); + emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); + } + /** * @notice Return the permissions flag that are associated with STO * @return bytes32 array diff --git a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol deleted file mode 100644 index 2dd9d28f5..000000000 --- a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpoint.sol +++ /dev/null @@ -1,109 +0,0 @@ -pragma solidity ^0.4.24; - -import "./EtherDividendCheckpoint.sol"; - -/** - * @title Checkpoint module for issuing ether dividends - */ -contract EtherDividendWithholdingCheckpoint is EtherDividendCheckpoint { - using SafeMath for uint256; - - // Mapping from address to withholding tax as a percentage * 10**16 - mapping (address => uint256) public withholdingTax; - - uint256 public totalWithheld; - mapping (uint256 => uint256) public dividendWithheld; - mapping (uint256 => uint256) public dividendWithheldReclaimed; - mapping (address => uint256) public investorWithheld; - - event EtherDividendWithholdingClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendWithholdingClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor (address _securityToken, address _polyAddress) public - EtherDividendCheckpoint(_securityToken, _polyAddress) - { - } - - /** - * @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); - for (uint256 i = 0; i < _investors.length; i++) { - withholdingTax[_investors[i]] = _withholding[i]; - } - } - - /** - * @notice Function to set withholding tax rates for investors - * @param _investors addresses of investor - * @param _withholding withholding tax for all investors (multiplied by 10**16) - */ - function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { - for (uint256 i = 0; i < _investors.length; i++) { - withholdingTax[_investors[i]] = _withholding; - } - } - - /** - * @notice Internal function for paying dividends - * @param _payee address of investor - * @param _dividend storage with previously issued dividends - * @param _dividendIndex Dividend to pay - */ - function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { - (uint256 claim, uint256 withheld) = calculateDividendWithholding(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - _dividend.claimedAmount = claim.add(_dividend.claimedAmount); - uint256 claimAfterWithheld = claim.sub(withheld); - if (claimAfterWithheld > 0) { - if (_payee.send(claimAfterWithheld)) { - totalWithheld = totalWithheld.add(withheld); - dividendWithheld[_dividendIndex] = dividendWithheld[_dividendIndex].add(withheld); - investorWithheld[_payee] = investorWithheld[_payee].add(withheld); - emit EtherDividendWithholdingClaimed(_payee, _dividendIndex, claim, withheld); - } else { - _dividend.claimed[_payee] = false; - emit EtherDividendWithholdingClaimFailed(_payee, _dividendIndex, claim, withheld); - } - } - } - - /** - * @notice Calculate amount of dividends claimable - * @param _dividendIndex Dividend to calculate - * @param _payee Affected investor address - * @return full claim - * @return amount to be withheld - */ - function calculateDividendWithholding(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee]) { - return (0, 0); - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); - uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); - uint256 withheld = claim.mul(uint256(10**18).sub(withholdingTax[_payee])).div(uint256(10**18)); - return (claim, withheld); - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) public onlyOwner { - uint256 remainingWithheld = dividendWithheld[_dividendIndex].sub(dividendWithheldReclaimed[_dividendIndex]); - dividendWithheldReclaimed[_dividendIndex] = dividendWithheld[_dividendIndex]; - msg.sender.transfer(remainingWithheld); - emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); - } - -} diff --git a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol deleted file mode 100644 index 2b5115df9..000000000 --- a/contracts/modules/Checkpoint/EtherDividendWithholdingCheckpointFactory.sol +++ /dev/null @@ -1,76 +0,0 @@ -pragma solidity ^0.4.24; - -import "./EtherDividendWithholdingCheckpoint.sol"; -import "../../interfaces/IModuleFactory.sol"; - -/** - * @title Factory for deploying EtherDividendCheckpoint module - */ -contract EtherDividendWithholdingCheckpointFactory is IModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - * @param _setupCost Setup cost of the module - * @param _usageCost Usage cost of the module - * @param _subscriptionCost Subscription cost of the module - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - IModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - - } - - /** - * @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) { - if(setupCost > 0) - require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); - return address(new EtherDividendWithholdingCheckpoint(msg.sender, address(polyToken))); - } - - /** - * @notice Type of the Module factory - */ - function getType() public view returns(uint8) { - return 4; - } - - /** - * @notice Get the name of the Module - */ - function getName() public view returns(bytes32) { - return bytes32("EthDividendWithholdingCheckpoint"); - } - - /** - * @notice Get the description of the Module - */ - function getDescription() public view returns(string) { - return "Create ETH dividends for token holders at a specific checkpoint, and allow tax to be withheld"; - } - - /** - * @notice Get the title of the Module - */ - function getTitle() public view returns(string) { - return "Ether Dividend Withholding Checkpoint"; - } - - /** - * @notice Get the Instructions that helped to used the module - */ - function getInstructions() public view returns(string) { - return "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created. Withholding tax is specified per investor (defaults to zero) and withheld from dividends"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](0); - return availableTags; - } -} diff --git a/test/ether_dividends.js b/test/ether_dividends.js index 2d828c3c2..043373a63 100644 --- a/test/ether_dividends.js +++ b/test/ether_dividends.js @@ -393,6 +393,10 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); + it("Set withholding tax of 20% on investor 2", async() => { + await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], BigNumber(20*10**16), {from: token_owner}); + }); + it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); @@ -451,11 +455,18 @@ contract('EtherDividendCheckpoint', accounts => { let investor1BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor2)); assert.equal(investor1BalanceAfter.sub(investor1Balance).toNumber(), web3.utils.toWei('0.5', 'ether')); - assert.equal(investor2BalanceAfter.sub(investor2Balance).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(investor2BalanceAfter.sub(investor2Balance).toNumber(), web3.utils.toWei('0.8', 'ether')); //Check fully claimed assert.equal((await I_EtherDividendCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei('1.5', 'ether')); }); + it("Issuer reclaims withholding tax", async() => { + let issuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendCheckpoint.withdrawWithholding(0, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0.2', 'ether')) + }); + it("Buy some tokens for account_temp (1 ETH)", async() => { // Add the Investor in to the whitelist @@ -488,7 +499,7 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends fails due to passed expiry", async() => { let errorThrown = false; await increaseTime(duration.days(12)); try { @@ -501,7 +512,7 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer reclaims dividend", async() => { let errorThrown = false; let tx = await I_EtherDividendCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); assert.equal((tx.logs[0].args._claimedAmount).toNumber(), web3.utils.toWei("1.5", "ether")); @@ -514,7 +525,7 @@ contract('EtherDividendCheckpoint', accounts => { } assert.ok(errorThrown, message); }); - + it("Buy some tokens for account_investor3 (7 ETH)", async() => { // Add the Investor in to the whitelist @@ -586,7 +597,7 @@ contract('EtherDividendCheckpoint', accounts => { } assert.ok(errorThrown, message); }); - + it("Issuer pushes remainder", async() => { let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); @@ -596,7 +607,7 @@ contract('EtherDividendCheckpoint', accounts => { let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); - assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('3', 'ether')); + assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('2.4', 'ether')); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); //Check fully claimed assert.equal((await I_EtherDividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); @@ -623,7 +634,7 @@ contract('EtherDividendCheckpoint', accounts => { } assert.ok(errorThrown, message); }); - + it("Create another new dividend with explicit", async() => { let errorThrown = false; let maturity = latestTime(); @@ -651,7 +662,7 @@ contract('EtherDividendCheckpoint', accounts => { } assert.ok(errorThrown, message); }); - + it("Create another new dividend with explicit", async() => { let errorThrown = false; let maturity = latestTime(); @@ -709,13 +720,16 @@ contract('EtherDividendCheckpoint', accounts => { let dividendAmount2 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor2); let dividendAmount3 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor3); let dividendAmount_temp = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_temp); - assert.equal(dividendAmount1.toNumber(), web3.utils.toWei("1", "ether")); - assert.equal(dividendAmount2.toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(dividendAmount3.toNumber(), web3.utils.toWei("7", "ether")); - assert.equal(dividendAmount_temp.toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); + assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("0.4", "ether")); + assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); + assert.equal(dividendAmount3[1].toNumber(), web3.utils.toWei("0", "ether")); + assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount_temp[1].toNumber(), web3.utils.toWei("0", "ether")); }); - it("Investor 2 claims dividend", async() => { let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); @@ -727,18 +741,17 @@ contract('EtherDividendCheckpoint', accounts => { let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('2', 'ether')); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('1.6', 'ether')); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); - - }); + }); it("Should issuer pushes investor 1 and temp investor", async() => { let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); + let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); await I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(3, [account_investor1, account_temp], {from: token_owner}); let investor1BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); @@ -747,7 +760,7 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); - assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); //Check fully claimed assert.equal((await I_EtherDividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); }); @@ -755,8 +768,8 @@ contract('EtherDividendCheckpoint', accounts => { it("should calculate dividend after the push dividend payment", async() => { let dividendAmount1 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor1); let dividendAmount2 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor2); - assert.equal(dividendAmount1.toNumber(), 0); - assert.equal(dividendAmount2.toNumber(), 0); + assert.equal(dividendAmount1[0].toNumber(), 0); + assert.equal(dividendAmount2[0].toNumber(), 0); }); it("Issuer unable to reclaim dividend (expiry not passed)", async() => { @@ -815,7 +828,7 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - + it("Should give the right dividend index", async() => { let index = await I_EtherDividendCheckpoint.getDividendIndex.call(3); assert.equal(index[0], 2); diff --git a/test/ether_withholding_dividends.js b/test/ether_withholding_dividends.js deleted file mode 100644 index 3c476425f..000000000 --- a/test/ether_withholding_dividends.js +++ /dev/null @@ -1,856 +0,0 @@ -import latestTime from './helpers/latestTime'; -import { duration, ensureException } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; -import { error } from 'util'; - -const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); -const TickerRegistry = artifacts.require('./TickerRegistry.sol'); -const STVersion = artifacts.require('./STVersionProxy001.sol'); -const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); -const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const EtherDividendWithholdingCheckpointFactory = artifacts.require('./EtherDividendWithholdingCheckpointFactory.sol'); -const EtherDividendWithholdingCheckpoint = artifacts.require('./EtherDividendWithholdingCheckpoint'); -const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); -const PolyTokenFaucet = artifacts.require('./PolyTokenFaucet.sol'); - -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port - -contract('EtherDividendWithholdingCheckpoint', accounts => { - - // Accounts Variable declaration - let account_polymath; - let account_issuer; - let token_owner; - let account_investor1; - let account_investor2; - let account_investor3; - let account_investor4; - let account_temp; - - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - - let message = "Transaction Should Fail!"; - - // Contract Instance Declaration - let I_GeneralPermissionManagerFactory; - let I_GeneralTransferManagerFactory; - let P_EtherDividendWithholdingCheckpointFactory; - let P_EtherDividendWithholdingCheckpoint; - let I_EtherDividendWithholdingCheckpointFactory; - let I_GeneralPermissionManager; - let I_EtherDividendWithholdingCheckpoint; - let I_GeneralTransferManager; - let I_ExchangeTransferManager; - let I_ModuleRegistry; - let I_TickerRegistry; - let I_SecurityTokenRegistry; - let I_STVersion; - let I_SecurityToken; - let I_PolyToken; - - // SecurityToken Details - const swarmHash = "dagwrgwgvwergwrvwrg"; - const name = "Team"; - const symbol = "sap"; - const tokenDetails = "This is equity type of issuance"; - const decimals = 18; - const contact = "team@polymath.network"; - let snapId; - // Module key - const delegateManagerKey = 1; - const transferManagerKey = 2; - const stoKey = 3; - const checkpointKey = 4; - - // Initial fee for ticker registry and security token registry - const initRegFee = 250 * Math.pow(10, 18); - - before(async() => { - // Accounts setup - account_polymath = accounts[0]; - account_issuer = accounts[1]; - - token_owner = account_issuer; - - account_investor1 = accounts[6]; - account_investor2 = accounts[7]; - account_investor3 = accounts[8]; - account_investor4 = accounts[9]; - account_temp = accounts[2]; - - // ----------- POLYMATH NETWORK Configuration ------------ - - // Step 0: Deploy the token Faucet and Mint tokens for token_owner - I_PolyToken = await PolyTokenFaucet.new(); - await I_PolyToken.getTokens((10000 * Math.pow(10, 18)), token_owner); - - // STEP 1: Deploy the ModuleRegistry - - I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); - - assert.notEqual( - I_ModuleRegistry.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "ModuleRegistry contract was not deployed" - ); - - // STEP 2: Deploy the GeneralTransferManagerFactory - - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); - - assert.notEqual( - I_GeneralTransferManagerFactory.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "GeneralTransferManagerFactory contract was not deployed" - ); - - // STEP 3: Deploy the GeneralDelegateManagerFactory - - I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); - - assert.notEqual( - I_GeneralPermissionManagerFactory.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "GeneralDelegateManagerFactory contract was not deployed" - ); - - // STEP 4: Deploy the EtherDividendWithholdingCheckpoint - I_EtherDividendWithholdingCheckpointFactory = await EtherDividendWithholdingCheckpointFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); - assert.notEqual( - I_EtherDividendWithholdingCheckpointFactory.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "EtherDividendWithholdingCheckpointFactory contract was not deployed" - ); - - // STEP 5: Register the Modules with the ModuleRegistry contract - - // (A) : Register the GeneralTransferManagerFactory - await I_ModuleRegistry.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - await I_ModuleRegistry.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); - - // (B) : Register the GeneralDelegateManagerFactory - await I_ModuleRegistry.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); - await I_ModuleRegistry.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); - - // (C) : Register the EtherDividendWithholdingCheckpointFactory - await I_ModuleRegistry.registerModule(I_EtherDividendWithholdingCheckpointFactory.address, { from: account_polymath }); - await I_ModuleRegistry.verifyModule(I_EtherDividendWithholdingCheckpointFactory.address, true, { from: account_polymath }); - - // (C) : Register the Paid EtherDividendWithholdingCheckpointFactory - await I_ModuleRegistry.registerModule(P_EtherDividendWithholdingCheckpointFactory.address, { from: account_polymath }); - await I_ModuleRegistry.verifyModule(P_EtherDividendWithholdingCheckpointFactory.address, true, { from: account_polymath }); - - // Step 6: Deploy the TickerRegistry - - I_TickerRegistry = await TickerRegistry.new(I_PolyToken.address, initRegFee, { from: account_polymath }); - - assert.notEqual( - I_TickerRegistry.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "TickerRegistry contract was not deployed", - ); - - // Step 7: Deploy the STversionProxy contract - - I_STVersion = await STVersion.new(I_GeneralTransferManagerFactory.address); - - assert.notEqual( - I_STVersion.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "STVersion contract was not deployed", - ); - - // Step 8: Deploy the SecurityTokenRegistry - - I_SecurityTokenRegistry = await SecurityTokenRegistry.new( - I_PolyToken.address, - I_ModuleRegistry.address, - I_TickerRegistry.address, - I_STVersion.address, - initRegFee, - { - from: account_polymath - }); - - assert.notEqual( - I_SecurityTokenRegistry.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "SecurityTokenRegistry contract was not deployed", - ); - - // Step 8: Set the STR in TickerRegistry - await I_TickerRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistry.address, {from: account_polymath}); - await I_ModuleRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistry.address, {from: account_polymath}); - - // Printing all the contract addresses - console.log(`\nPolymath Network Smart Contracts Deployed:\n - ModuleRegistry: ${I_ModuleRegistry.address}\n - GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address}\n - EtherDividendWithholdingCheckpointFactory: ${I_EtherDividendWithholdingCheckpointFactory.address}\n - GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address}\n - TickerRegistry: ${I_TickerRegistry.address}\n - STVersionProxy_001: ${I_STVersion.address}\n - SecurityTokenRegistry: ${I_SecurityTokenRegistry.address}\n - `); - }); - - describe("Generate the SecurityToken", async() => { - - it("Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.approve(I_TickerRegistry.address, initRegFee, { from: token_owner }); - let tx = await I_TickerRegistry.registerTicker(token_owner, symbol, contact, swarmHash, { from : token_owner }); - assert.equal(tx.logs[0].args._owner, token_owner); - assert.equal(tx.logs[0].args._symbol, symbol.toUpperCase()); - }); - - it("Should generate the new security token with the same symbol as registered above", async () => { - await I_PolyToken.approve(I_SecurityTokenRegistry.address, initRegFee, { from: token_owner }); - let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner, gas: 85000000 }); - - // Verify the successful generation of the security token - assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); - - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const LogAddModule = await I_SecurityToken.allEvents(); - const log = await new Promise(function(resolve, reject) { - LogAddModule.watch(function(error, log){ resolve(log);}); - }); - - // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._type.toNumber(), 2); - assert.equal( - web3.utils.toAscii(log.args._name) - .replace(/\u0000/g, ''), - "GeneralTransferManager" - ); - LogAddModule.stopWatching(); - }); - - it("Should intialize the auto attached modules", async () => { - let moduleData = await I_SecurityToken.modules(2, 0); - I_GeneralTransferManager = GeneralTransferManager.at(moduleData[1]); - - assert.notEqual( - I_GeneralTransferManager.address.valueOf(), - "0x0000000000000000000000000000000000000000", - "GeneralTransferManager contract was not deployed", - ); - - }); - - it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { - let errorThrown = false; - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - try { - const tx = await I_SecurityToken.addModule(P_EtherDividendWithholdingCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, true, { from: token_owner }); - } catch(error) { - console.log(` tx -> failed because Token is not paid`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Should successfully attach the EtherDividendWithholdingCheckpoint 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_EtherDividendWithholdingCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, true, { from: token_owner }); - assert.equal(tx.logs[3].args._type.toNumber(), checkpointKey, "EtherDividendWithholdingCheckpoint doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[3].args._name) - .replace(/\u0000/g, ''), - "EtherDividendWithholdingCheckpoint", - "EtherDividendWithholdingCheckpoint module was not added" - ); - P_EtherDividendWithholdingCheckpoint = EtherDividendWithholdingCheckpoint.at(tx.logs[3].args._module); - await revertToSnapshot(snapId); - }); - - it("Should successfully attach the EtherDividendWithholdingCheckpoint with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_EtherDividendWithholdingCheckpointFactory.address, "", 0, 0, true, { from: token_owner }); - assert.equal(tx.logs[2].args._type.toNumber(), checkpointKey, "EtherDividendWithholdingCheckpoint doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), - "EtherDividendWithholdingCheckpoint", - "EtherDividendWithholdingCheckpoint module was not added" - ); - I_EtherDividendWithholdingCheckpoint = EtherDividendWithholdingCheckpoint.at(tx.logs[2].args._module); - }); - }); - - describe("Check Dividend payouts", async() => { - - it("Buy some tokens for account_investor1 (1 ETH)", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, - { - from: account_issuer, - gas: 500000 - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Jump time - await increaseTime(5000); - - // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('1', 'ether') - ); - }); - - it("Buy some tokens for account_investor2 (2 ETH)", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(30), - true, - { - from: account_issuer, - gas: 500000 - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('2', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), - web3.utils.toWei('2', 'ether') - ); - }); - - it("Should fail in creating the dividend", async() => { - let errorThrown = false; - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - try { - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner}); - } catch(error) { - console.log(` tx -> failed because msg.value = 0`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Should fail in creating the dividend", async() => { - let errorThrown = false; - let maturity = latestTime(); - let expiry = latestTime() - duration.days(10); - try { - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); - } catch(error) { - console.log(` tx -> failed because maturity > expiry`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Should fail in creating the dividend", async() => { - let errorThrown = false; - let maturity = latestTime() - duration.days(2); - let expiry = latestTime() - duration.days(1); - try { - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); - } catch(error) { - console.log(` tx -> failed because now > expiry`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Create new dividend", async() => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); - }); - - it("Investor 1 transfers his token balance to investor 2", async() => { - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), {from: account_investor1}); - assert.equal(await I_SecurityToken.balanceOf(account_investor1), 0); - assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('3', 'ether')); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); - } catch(error) { - console.log(` tx -> failed because dividend index has maturity in future`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let errorThrown = false; - // Increase time by 2 day - await increaseTime(duration.days(2)); - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: account_temp}); - } catch(error) { - console.log(` tx -> failed because msg.sender is not the owner`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); - } catch(error) { - console.log(` tx -> failed because dividend index is greator than the dividend array length`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); - let investor1BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor2)); - assert.equal(investor1BalanceAfter.sub(investor1Balance).toNumber(), web3.utils.toWei('0.5', 'ether')); - assert.equal(investor2BalanceAfter.sub(investor2Balance).toNumber(), web3.utils.toWei('1', 'ether')); - //Check fully claimed - assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei('1.5', 'ether')); - }); - - it("Buy some tokens for account_temp (1 ETH)", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_temp, - latestTime(), - latestTime(), - latestTime() + duration.days(20), - true, - { - from: account_issuer, - gas: 500000 - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_temp.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_temp, web3.utils.toWei('1', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_temp)).toNumber(), - web3.utils.toWei('1', 'ether') - ); - }); - - it("Create new dividend", async() => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let errorThrown = false; - await increaseTime(duration.days(12)); - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); - } catch(error) { - console.log(` tx -> failed because dividend index has passed its expiry`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let errorThrown = false; - let tx = await I_EtherDividendWithholdingCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); - assert.equal((tx.logs[0].args._claimedAmount).toNumber(), web3.utils.toWei("1.5", "ether")); - try { - await I_EtherDividendWithholdingCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); - } catch(error) { - console.log(` tx -> failed because dividend index has already reclaimed`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Buy some tokens for account_investor3 (7 ETH)", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('7', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), - web3.utils.toWei('7', 'ether') - ); - }); - - it("Create another new dividend", async() => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendWithholdingCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); - }); - - it("should investor 3 claims dividend", async() => { - let errorThrown = false; - let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); - try { - await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(5, {from: account_investor3, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because dividend index is not valid`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Should investor 3 claims dividend", async() => { - let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3Balance = BigNumber(await web3.eth.getBalance(account_investor3)); - await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(2, {from: account_investor3, gasPrice: 0}); - let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); - assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); - assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), web3.utils.toWei('7', 'ether')); - }); - - it("should investor 3 claims dividend", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(2, {from: account_investor3, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because investor already claimed the dividend`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer pushes remainder", async() => { - let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); - await I_EtherDividendWithholdingCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); - let investor1BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); - assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); - assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('3', 'ether')); - assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); - //Check fully claimed - assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); - }); - - it("Investor 2 transfers 1 ETH of his token balance to investor 1", async() => { - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), {from: account_investor2}); - assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); - assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('2', 'ether')); - assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); - }); - - it("Create another new dividend with explicit", async() => { - let errorThrown = false; - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); - let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); - try { - tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: 0}); - } catch(error) { - console.log(` tx -> failed because msg.value is 0`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Create another new dividend with explicit", async() => { - let errorThrown = false; - let maturity = latestTime(); - let expiry = latestTime() - duration.days(10); - try { - tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - } catch(error) { - console.log(` tx -> failed because maturity > expiry`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Create another new dividend with explicit", async() => { - let errorThrown = false; - let maturity = latestTime() - duration.days(5); - let expiry = latestTime() - duration.days(2); - try { - tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - } catch(error) { - console.log(` tx -> failed because now > expiry`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Create another new dividend with explicit", async() => { - let errorThrown = false; - let maturity = latestTime(); - let expiry = latestTime() + duration.days(2); - try { - tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - } catch(error) { - console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Create another new dividend with explicit", async() => { - let maturity = latestTime(); - let expiry = latestTime() + duration.days(10); - let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); - tx = await I_EtherDividendWithholdingCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); - }); - - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { - let errorThrown = false; - let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(4, [account_investor2, account_investor1],{from: account_investor2, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because not called by the owner`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { - let errorThrown = false; - let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); - let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); - let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); - try { - await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(6, [account_investor2, account_investor1],{from: token_owner, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because dividend index is not valid`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("should calculate dividend before the push dividend payment", async() => { - let dividendAmount1 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor1); - let dividendAmount2 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor2); - let dividendAmount3 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor3); - let dividendAmount_temp = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_temp); - assert.equal(dividendAmount1.toNumber(), web3.utils.toWei("1", "ether")); - assert.equal(dividendAmount2.toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(dividendAmount3.toNumber(), web3.utils.toWei("7", "ether")); - assert.equal(dividendAmount_temp.toNumber(), web3.utils.toWei("1", "ether")); - }); - - - it("Investor 2 claims dividend", async() => { - let investor1Balance = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2Balance = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3Balance = BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalance = BigNumber(await web3.eth.getBalance(account_temp)); - await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(3, {from: account_investor2, gasPrice: 0}); - let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); - assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('2', 'ether')); - assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); - assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); - - }); - - - it("Should issuer pushes investor 1 and temp investor", async() => { - let investor1BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter1 = BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); - await I_EtherDividendWithholdingCheckpoint.pushDividendPaymentToAddresses(3, [account_investor1, account_temp], {from: token_owner}); - let investor1BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor1)); - let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); - let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); - let tempBalanceAfter2 = BigNumber(await web3.eth.getBalance(account_temp)); - assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); - assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); - assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); - assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); - //Check fully claimed - assert.equal((await I_EtherDividendWithholdingCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); - }); - - it("should calculate dividend after the push dividend payment", async() => { - let dividendAmount1 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor1); - let dividendAmount2 = await I_EtherDividendWithholdingCheckpoint.calculateDividend.call(3, account_investor2); - assert.equal(dividendAmount1.toNumber(), 0); - assert.equal(dividendAmount2.toNumber(), 0); - }); - - it("Issuer unable to reclaim dividend (expiry not passed)", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner}); - } catch(error) { - console.log(`Tx Failed because expiry is in the future ${0}. Test Passed Successfully`); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - }); - - it("Issuer is able to reclaim dividend after expiry", async() => { - let errorThrown = false; - await increaseTime(11 * 24 * 60 * 60); - try { - await I_EtherDividendWithholdingCheckpoint.reclaimDividend(8, {from: token_owner, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because dividend index is not valid`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Issuer is able to reclaim dividend after expiry", async() => { - let tokenOwnerBalance = BigNumber(await web3.eth.getBalance(token_owner)); - await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner, gasPrice: 0}); - let tokenOwnerAfter = BigNumber(await web3.eth.getBalance(token_owner)); - assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei('7', 'ether')); - }); - - it("Issuer is able to reclaim dividend after expiry", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.reclaimDividend(3, {from: token_owner, gasPrice: 0}); - } catch(error) { - console.log(` tx -> failed because dividend are already reclaimed`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - - it("Investor 3 unable to pull dividend after expiry", async() => { - let errorThrown = false; - try { - await I_EtherDividendWithholdingCheckpoint.pullDividendPayment(3, {from: account_investor3, gasPrice: 0}); - } catch(error) { - console.log(`Tx Failed because expiry is in the past ${0}. Test Passed Successfully`); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - - }); - - it("Should give the right dividend index", async() => { - let index = await I_EtherDividendWithholdingCheckpoint.getDividendIndex.call(3); - assert.equal(index[0], 2); - }); - - it("Should give the right dividend index", async() => { - let index = await I_EtherDividendWithholdingCheckpoint.getDividendIndex.call(8); - assert.equal(index.length, 0); - }); - - it("Get the init data", async() => { - let tx = await I_EtherDividendWithholdingCheckpoint.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ''),0); - }); - - it("Should get the listed permissions", async() => { - let tx = await I_EtherDividendWithholdingCheckpoint.getPermissions.call(); - assert.equal(tx.length,0); - }); - - describe("Test cases for the EtherDividendWithholdingCheckpointFactory", async() => { - it("should get the exact details of the factory", async() => { - assert.equal((await I_EtherDividendWithholdingCheckpointFactory.setupCost.call()).toNumber(), 0); - assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getType.call(), 4); - assert.equal(web3.utils.toAscii(await I_EtherDividendWithholdingCheckpointFactory.getName.call()) - .replace(/\u0000/g, ''), - "EtherDividendWithholdingCheckpoint", - "Wrong Module added"); - assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getDescription.call(), - "Create ETH dividends for token holders at a specific checkpoint", - "Wrong Module added"); - assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getTitle.call(), - "Ether Dividend Checkpoint", - "Wrong Module added"); - assert.equal(await I_EtherDividendWithholdingCheckpointFactory.getInstructions.call(), - "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created", - "Wrong Module added"); - let tags = await I_EtherDividendWithholdingCheckpointFactory.getTags.call(); - assert.equal(tags.length, 0); - - }); - }); - - }); - -}); From 2de1fd954c64ccc9da3e3dc9a27950212a6ccece Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Wed, 27 Jun 2018 09:25:49 +0100 Subject: [PATCH 03/22] Improve testing --- test/ether_dividends.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/ether_dividends.js b/test/ether_dividends.js index 043373a63..c05ecc632 100644 --- a/test/ether_dividends.js +++ b/test/ether_dividends.js @@ -467,6 +467,13 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0.2', 'ether')) }); + it("No more withholding tax to withdraw", async() => { + let issuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendCheckpoint.withdrawWithholding(0, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0', 'ether')) + }); + it("Buy some tokens for account_temp (1 ETH)", async() => { // Add the Investor in to the whitelist @@ -526,6 +533,13 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); + it("Still no more withholding tax to withdraw", async() => { + let issuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendCheckpoint.withdrawWithholding(0, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0', 'ether')) + }); + it("Buy some tokens for account_investor3 (7 ETH)", async() => { // Add the Investor in to the whitelist @@ -586,6 +600,13 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), web3.utils.toWei('7', 'ether')); }); + it("Still no more withholding tax to withdraw", async() => { + let issuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendCheckpoint.withdrawWithholding(0, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0', 'ether')) + }); + it("should investor 3 claims dividend", async() => { let errorThrown = false; try { @@ -613,6 +634,13 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal((await I_EtherDividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); }); + it("Issuer withdraws new withholding tax", async() => { + let issuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await I_EtherDividendCheckpoint.withdrawWithholding(2, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await web3.eth.getBalance(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0.6', 'ether')) + }); + it("Investor 2 transfers 1 ETH of his token balance to investor 1", async() => { await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), {from: account_investor2}); assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); From 318393ffa2e5c9926914aff2a348c2fe63617f99 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Fri, 29 Jun 2018 13:07:29 +0100 Subject: [PATCH 04/22] Add excluded addresses for Ether dividends --- .../Checkpoint/EtherDividendCheckpoint.sol | 36 ++++++++++++---- test/ether_dividends.js | 43 ++++++++++--------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 7ea63e305..2912198a8 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -11,6 +11,8 @@ import "openzeppelin-solidity/contracts/math/Math.sol"; contract EtherDividendCheckpoint is ICheckpoint { using SafeMath for uint256; + uint256 public EXCLUDED_ADDRESS_LIMIT = 50; + struct Dividend { uint256 checkpointId; uint256 created; // Time at which the dividend was created @@ -21,6 +23,7 @@ contract EtherDividendCheckpoint is ICheckpoint { uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) bool reclaimed; mapping (address => bool) claimed; // List of addresses which have claimed dividend + mapping (address => bool) excluded; // List of addresses which cannot claim dividends } // List of all dividends @@ -98,13 +101,18 @@ contract EtherDividendCheckpoint is ICheckpoint { * @param _maturity Time from which dividend can be paid * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer */ - function createDividend(uint256 _maturity, uint256 _expiry) payable public onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry, address[] _excluded) payable public onlyOwner { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT); require(_expiry > _maturity); require(_expiry > now); require(msg.value > 0); uint256 dividendIndex = dividends.length; uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); uint256 currentSupply = ISecurityToken(securityToken).totalSupply(); + uint256 excludedSupply = 0; + for (uint256 i = 0; i < _excluded.length; i++) { + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); + } dividends.push( Dividend( checkpointId, @@ -113,10 +121,13 @@ contract EtherDividendCheckpoint is ICheckpoint { _expiry, msg.value, 0, - currentSupply, + currentSupply.sub(excludedSupply), false ) ); + for (uint256 j = 0; j < _excluded.length; j++) { + dividends[dividends.length - 1].excluded[_excluded[j]] = true; + } emit EtherDividendDeposited(msg.sender, checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); } @@ -126,13 +137,18 @@ contract EtherDividendCheckpoint is ICheckpoint { * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer * @param _checkpointId Id of the checkpoint from which to issue dividend */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId) payable public onlyOwner { + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT); require(_expiry > _maturity); require(_expiry > now); require(msg.value > 0); require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); uint256 dividendIndex = dividends.length; uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); + uint256 excludedSupply = 0; + for (uint256 i = 0; i < _excluded.length; i++) { + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); + } dividends.push( Dividend( _checkpointId, @@ -141,10 +157,13 @@ contract EtherDividendCheckpoint is ICheckpoint { _expiry, msg.value, 0, - currentSupply, + currentSupply.sub(excludedSupply), false ) ); + for (uint256 j = 0; j < _excluded.length; j++) { + dividends[dividends.length - 1].excluded[_excluded[j]] = true; + } emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); } @@ -156,7 +175,7 @@ contract EtherDividendCheckpoint is ICheckpoint { function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public onlyOwner validDividendIndex(_dividendIndex) { Dividend storage dividend = dividends[_dividendIndex]; for (uint256 i = 0; i < _payees.length; i++) { - if (!dividend.claimed[_payees[i]]) { + if ((!dividend.claimed[_payees[i]]) && (!dividend.excluded[_payees[i]])) { _payDividend(_payees[i], dividend, _dividendIndex); } } @@ -173,7 +192,7 @@ contract EtherDividendCheckpoint is ICheckpoint { uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { address payee = ISecurityToken(securityToken).investors(i); - if (!dividend.claimed[payee]) { + if ((!dividend.claimed[payee]) && (!dividend.excluded[payee])) { _payDividend(payee, dividend, _dividendIndex); } } @@ -186,7 +205,8 @@ contract EtherDividendCheckpoint is ICheckpoint { function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) { Dividend storage dividend = dividends[_dividendIndex]; - require(!dividend.claimed[msg.sender], "Dividend already reclaimed"); + require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); + require(!dividend.excluded[msg.sender], "msg.sender excluded from Dividend"); _payDividend(msg.sender, dividend, _dividendIndex); } @@ -236,7 +256,7 @@ contract EtherDividendCheckpoint is ICheckpoint { */ function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee]) { + if (dividend.claimed[_payee] || dividend.excluded[_payee]) { return (0, 0); } uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); diff --git a/test/ether_dividends.js b/test/ether_dividends.js index c05ecc632..d75cc4283 100644 --- a/test/ether_dividends.js +++ b/test/ether_dividends.js @@ -356,7 +356,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because msg.value = 0`.grey); ensureException(error); @@ -370,7 +370,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -384,7 +384,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -400,7 +400,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); }); @@ -502,7 +502,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); @@ -568,7 +568,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create another new dividend", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); }); @@ -648,13 +648,13 @@ contract('EtherDividendCheckpoint', accounts => { assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with no value - fails", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(2); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: 0}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: 0}); } catch(error) { console.log(` tx -> failed because msg.value is 0`.grey); ensureException(error); @@ -663,12 +663,12 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with bad maturity - fails", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -677,12 +677,12 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with bad expirty - fails", async() => { let errorThrown = false; let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -691,12 +691,12 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with bad checkpoint in the future - fails", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -705,15 +705,15 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit checkpoint and excluding account_investor1", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [account_investor1], {from: token_owner, value: web3.utils.toWei('10', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); }); - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + it("Non-owner pushes investor 1 - fails", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -728,7 +728,7 @@ contract('EtherDividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + it("issuer pushes investor 1 with bad dividend index - fails", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -748,7 +748,8 @@ contract('EtherDividendCheckpoint', accounts => { let dividendAmount2 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor2); let dividendAmount3 = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_investor3); let dividendAmount_temp = await I_EtherDividendCheckpoint.calculateDividend.call(3, account_temp); - assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("1", "ether")); + //1 has 1/11th, 2 has 2/11th, 3 has 7/11th, temp has 1/11th, but 1 is excluded + assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("0.4", "ether")); @@ -785,12 +786,12 @@ contract('EtherDividendCheckpoint', accounts => { let investor2BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor2)); let investor3BalanceAfter2 = BigNumber(await web3.eth.getBalance(account_investor3)); let tempBalanceAfter2 = BigNumber(await web3.eth.getBalance(account_temp)); - assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); //Check fully claimed - assert.equal((await I_EtherDividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); + assert.equal((await I_EtherDividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('3', 'ether')); }); it("should calculate dividend after the push dividend payment", async() => { From 282ea45d258ece77bee92d67bea6d82519cafe7e Mon Sep 17 00:00:00 2001 From: Stephane Gosselin Date: Tue, 3 Jul 2018 12:47:31 +0200 Subject: [PATCH 05/22] ETH withholding CLI --- demo/checkpoint/ethExplorer.js | 61 ++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/demo/checkpoint/ethExplorer.js b/demo/checkpoint/ethExplorer.js index dd109d377..cb57a1512 100644 --- a/demo/checkpoint/ethExplorer.js +++ b/demo/checkpoint/ethExplorer.js @@ -34,7 +34,7 @@ try{ } - +const DEFAULT_GAS_PRICE = 80000000000; const Web3 = require('web3'); if (typeof web3 !== 'undefined') { @@ -105,11 +105,11 @@ async function start_explorer(){ } }); - let options = ['Mint tokens','Transfer tokens', - 'Explore account at checkpoint', 'Explore total supply at checkpoint', - 'Create checkpoint', 'Calculate Dividends', 'Calculate Dividends at a checkpoint', 'Push dividends to account', 'Pull dividends to account', 'Explore ETH balance', - 'Reclaimed dividends after expiry']; - let index = readlineSync.keyInSelect(options, 'What do you want to do?'); + let options = ['Mint tokens', 'Transfer tokens', 'Explore account at checkpoint', + 'Explore total supply at checkpoint', 'Create checkpoint', 'Issue Dividends', + 'Issue Dividends at a checkpoint', 'Tax Withholding', 'Push dividends to account', + 'Pull dividends to account', 'Explore ETH balance', 'Reclaimed dividends after expiry', 'Exit']; + let index = readlineSync.keyInSelect(options, 'What do you want to do?', {cancel: false}); console.log("Selected:",options[index]); switch(index){ case 0: @@ -152,16 +152,19 @@ async function start_explorer(){ } break; case 7: + await withholdingTax(); + break; + case 8: //Create dividends let _checkpoint3 = readlineSync.question('Distribute dividends at checkpoint: '); let _address2 = readlineSync.question('Enter address to push dividends to (ex- add1,add2,add3,...): '); await pushDividends(_checkpoint3,_address2); break; - case 8: + case 9: let _checkpoint7 = readlineSync.question('Distribute dividends at checkpoint: '); await pullDividends(_checkpoint7); break; - case 9: + case 10: //explore eth balance let _checkpoint4 = readlineSync.question('Enter checkpoint to explore: '); let _address3 = readlineSync.question('Enter address to explore: '); @@ -176,9 +179,12 @@ async function start_explorer(){ console.log("Sorry Future checkpoints are not allowed"); } break; - case 10: + case 11: let _checkpoint5 = readlineSync.question('Enter the checkpoint to explore: '); await reclaimedDividend(_checkpoint5); + break; + case 12: + process.exit(); } //Restart @@ -220,7 +226,7 @@ async function createDividends(ethDividend){ let expiryTime = readlineSync.question('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = '+(time+duration.minutes(10))+' ): '); if(expiryTime == "") expiryTime = time+duration.minutes(10); //Send eth dividends - await etherDividendCheckpoint.methods.createDividend(time, expiryTime) + await etherDividendCheckpoint.methods.createDividend(time, expiryTime,[]) .send({ from: Issuer, value: web3.utils.toWei(ethDividend,"ether"), gas:2500000 }) .on('transactionHash', function(hash){ console.log(` @@ -236,6 +242,39 @@ async function createDividends(ethDividend){ }) } +async function withholdingTax() { + let GAS + let options = ['Set a % to withhold from the dividends sent to an address', 'Withdraw all withheld dividends', 'Return to main menu']; + let index = readlineSync.keyInSelect(options, 'What do you want to do?', {cancel: false}); + console.log("Selected:",options[index]); + switch (index) { + case 0: + let addressT = readlineSync.question('Enter the address of the investor for which to withhold dividends: '); + let percentT = readlineSync.question('Enter the percentage of dividends to withhold (number between 0-100): '); + percentT = web3.utils.toWei((percentT * 10).toString(), 'milli'); + GAS = Math.round(1.2 * (await etherDividendCheckpoint.methods.setWithholdingFixed([addressT], percentT).estimateGas({from: Issuer}))); + console.log(chalk.black.bgYellowBright(`---- Transaction executed: setWithholdingFixed - Gas limit provided: ${GAS} ----`)); + await etherDividendCheckpoint.methods.setWithholdingFixed([addressT], percentT).send({from: Issuer, gas: GAS, gasPrice: DEFAULT_GAS_PRICE }) + .on('receipt', function(receipt){ + console.log(chalk.green(`\nSuccessfully set tax withholding of ${web3.utils.fromWei(percentT, 'milli')/10}% for ${addressT}.`)); + }); + break; + case 1: + // Withdraw + let divIndexT = readlineSync.question('Enter the index of the dividend to withdraw: '); + GAS = Math.round(1.2 * (await etherDividendCheckpoint.methods.withdrawWithholding(divIndexT).estimateGas({from: Issuer}))); + console.log(chalk.black.bgYellowBright(`---- Transaction executed: withdrawWithholding - Gas limit provided: ${GAS} ----`)); + await etherDividendCheckpoint.methods.withdrawWithholding(divIndexT).send({from: Issuer, gas: GAS, gasPrice: DEFAULT_GAS_PRICE }) + .on('receipt', function(receipt){ + let val = receipt.events.EtherDividendWithholdingWithdrawn.returnValues._withheldAmount; + console.log(chalk.green(`\nSuccessfully withdrew ${val} ETH from dividend ${divIndexT} tax withholding to ${Issuer}.`)); + }); + break; + case 2: + return; + break; + } +} async function createDividendWithCheckpoint(ethDividend, _checkpointId) { @@ -274,7 +313,7 @@ async function createDividendWithCheckpoint(ethDividend, _checkpointId) { let _dividendStatus = await etherDividendCheckpoint.methods.getDividendIndex(_checkpointId).call(); if (_dividendStatus.length != 1) { //Send eth dividends - await etherDividendCheckpoint.methods.createDividendWithCheckpoint(time, expiryTime, _checkpointId) + await etherDividendCheckpoint.methods.createDividendWithCheckpoint(time, expiryTime, _checkpointId, []) .send({ from: Issuer, value: web3.utils.toWei(ethDividend,"ether"), gas:2500000 }) .on('transactionHash', function(hash){ console.log(` From 3f258f316a2fce3d59ca294a3ed0bc91fe739794 Mon Sep 17 00:00:00 2001 From: Pablo Ruiz Date: Tue, 3 Jul 2018 16:21:35 -0300 Subject: [PATCH 06/22] fixed exception on CLI while creating dividend --- demo/checkpoint/ethExplorer.js | 3 ++- package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/demo/checkpoint/ethExplorer.js b/demo/checkpoint/ethExplorer.js index 4fc961394..1d093ea58 100644 --- a/demo/checkpoint/ethExplorer.js +++ b/demo/checkpoint/ethExplorer.js @@ -172,9 +172,10 @@ async function start_explorer(){ let _dividendIndex = await etherDividendCheckpoint.methods.getDividendIndex(_checkpoint4).call(); if (_dividendIndex.length == 1) { let divsAtCheckpoint = await etherDividendCheckpoint.methods.calculateDividend(_dividendIndex[0],_address3).call({ from: Issuer}); + //console.log(divsAtCheckpoint); console.log(` ETH Balance: ${web3.utils.fromWei(await web3.eth.getBalance(_address3),"ether")} ETH - Dividends owed at checkpoint ${_checkpoint4}: ${web3.utils.fromWei(divsAtCheckpoint,"ether")} ETH + Dividends owed at checkpoint ${_checkpoint4}: ${web3.utils.fromWei(divsAtCheckpoint[0],"ether")} ETH `) } else { console.log("Sorry Future checkpoints are not allowed"); diff --git a/package-lock.json b/package-lock.json index e131de390..9367a9118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10216,9 +10216,9 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, "truffle": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.11.tgz", - "integrity": "sha512-VNhc6jexZeM92sNJJr4U8ln3uJ/mJEQO/0y9ZLYc4pccyIskPtl+3r4mzymgGM/Mq5v6MpoQVD6NZgHUVKX+Dw==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.13.tgz", + "integrity": "sha1-vydYaYi0/4RWPt+/MrR5QUCKdq0=", "dev": true, "requires": { "mocha": "4.1.0", diff --git a/package.json b/package.json index d95c58fe0..20d0bc96d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "sol-merger": "^0.1.2", "solidity-coverage": "^0.5.0", "solium": "^1.1.6", - "truffle": "^4.1.11", + "truffle": "^4.1.13", "truffle-wallet-provider": "0.0.5" } } From 33f8552022f31ab1019a8702118a33b8148e5b7e Mon Sep 17 00:00:00 2001 From: pabloruiz55 Date: Tue, 3 Jul 2018 16:32:26 -0300 Subject: [PATCH 07/22] deleted console log --- demo/checkpoint/ethExplorer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/checkpoint/ethExplorer.js b/demo/checkpoint/ethExplorer.js index 1d093ea58..2d1f73ee5 100644 --- a/demo/checkpoint/ethExplorer.js +++ b/demo/checkpoint/ethExplorer.js @@ -172,7 +172,6 @@ async function start_explorer(){ let _dividendIndex = await etherDividendCheckpoint.methods.getDividendIndex(_checkpoint4).call(); if (_dividendIndex.length == 1) { let divsAtCheckpoint = await etherDividendCheckpoint.methods.calculateDividend(_dividendIndex[0],_address3).call({ from: Issuer}); - //console.log(divsAtCheckpoint); console.log(` ETH Balance: ${web3.utils.fromWei(await web3.eth.getBalance(_address3),"ether")} ETH Dividends owed at checkpoint ${_checkpoint4}: ${web3.utils.fromWei(divsAtCheckpoint[0],"ether")} ETH From 9b0e46b008840efab40fbba0fed34a2cdb890730 Mon Sep 17 00:00:00 2001 From: Stephane Gosselin Date: Wed, 4 Jul 2018 11:21:15 -0400 Subject: [PATCH 08/22] log withholding in ETH Balance --- demo/checkpoint/ethExplorer.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/demo/checkpoint/ethExplorer.js b/demo/checkpoint/ethExplorer.js index 4fc961394..cc5775ef9 100644 --- a/demo/checkpoint/ethExplorer.js +++ b/demo/checkpoint/ethExplorer.js @@ -171,10 +171,17 @@ async function start_explorer(){ let _address3 = readlineSync.question('Enter address to explore: '); let _dividendIndex = await etherDividendCheckpoint.methods.getDividendIndex(_checkpoint4).call(); if (_dividendIndex.length == 1) { - let divsAtCheckpoint = await etherDividendCheckpoint.methods.calculateDividend(_dividendIndex[0],_address3).call({ from: Issuer}); + let div = await etherDividendCheckpoint.methods.dividends(_dividendIndex[0]).call(); + let res = await etherDividendCheckpoint.methods.calculateDividend(_dividendIndex[0],_address3).call({ from: Issuer}); + let claim = new BigNumber(res[0]); + let withheld = new BigNumber(res[1]); + let percent = withheld.dividedBy(claim).times(100); console.log(` - ETH Balance: ${web3.utils.fromWei(await web3.eth.getBalance(_address3),"ether")} ETH - Dividends owed at checkpoint ${_checkpoint4}: ${web3.utils.fromWei(divsAtCheckpoint,"ether")} ETH + ETH Balance: ${web3.utils.fromWei((await web3.eth.getBalance(_address3)).toString(),"ether")} ETH + Dividend total size: ${web3.utils.fromWei((div.amount).toString(),"ether")} ETH + Dividends owed to investor at checkpoint ${_checkpoint4}: ${web3.utils.fromWei((claim).toString(),"ether")} ETH + Dividends withheld at checkpoint ${_checkpoint4}: ${web3.utils.fromWei((withheld).toString(),"ether")} ETH + Tax withholding percentage: ${percent}% `) } else { console.log("Sorry Future checkpoints are not allowed"); From 7550c0354fc95d559b7b4953f624e48642150bc8 Mon Sep 17 00:00:00 2001 From: Stephane Gosselin Date: Wed, 4 Jul 2018 13:32:18 -0400 Subject: [PATCH 09/22] add address dividend blacklist --- demo/checkpoint/ethExplorer.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/demo/checkpoint/ethExplorer.js b/demo/checkpoint/ethExplorer.js index cc5775ef9..a32bd6b18 100644 --- a/demo/checkpoint/ethExplorer.js +++ b/demo/checkpoint/ethExplorer.js @@ -234,9 +234,10 @@ async function createDividends(ethDividend){ let time = (await web3.eth.getBlock('latest')).timestamp; let expiryTime = readlineSync.question('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = '+(time+duration.minutes(10))+' ): '); + let blacklist = readlineSync.question('Enter an address to blacklist from dividend distribution: '); if(expiryTime == "") expiryTime = time+duration.minutes(10); //Send eth dividends - let createDividendAction = etherDividendCheckpoint.methods.createDividend(time, expiryTime, []); + let createDividendAction = etherDividendCheckpoint.methods.createDividend(time, expiryTime, [blacklist]); GAS = await common.estimateGas(createDividendAction, Issuer, 1.2, web3.utils.toWei(ethDividend,"ether")); await createDividendAction.send({ from: Issuer, value: web3.utils.toWei(ethDividend,"ether"), gas: GAS }) .on('transactionHash', function(hash){ @@ -272,14 +273,19 @@ async function withholdingTax() { break; case 1: // Withdraw - let divIndexT = readlineSync.question('Enter the index of the dividend to withdraw: '); - GAS = Math.round(1.2 * (await etherDividendCheckpoint.methods.withdrawWithholding(divIndexT).estimateGas({from: Issuer}))); - console.log(chalk.black.bgYellowBright(`---- Transaction executed: withdrawWithholding - Gas limit provided: ${GAS} ----`)); - await etherDividendCheckpoint.methods.withdrawWithholding(divIndexT).send({from: Issuer, gas: GAS, gasPrice: DEFAULT_GAS_PRICE }) - .on('receipt', function(receipt){ - let val = receipt.events.EtherDividendWithholdingWithdrawn.returnValues._withheldAmount; - console.log(chalk.green(`\nSuccessfully withdrew ${val} ETH from dividend ${divIndexT} tax withholding to ${Issuer}.`)); - }); + let checkpointId = readlineSync.question('Enter the checkpoint at which to withdraw: '); + let dividendIndex = await etherDividendCheckpoint.methods.getDividendIndex(checkpointId).call(); + if (dividendIndex.length == 1) { + GAS = Math.round(1.2 * (await etherDividendCheckpoint.methods.withdrawWithholding(dividendIndex[0]).estimateGas({from: Issuer}))); + console.log(chalk.black.bgYellowBright(`---- Transaction executed: withdrawWithholding - Gas limit provided: ${GAS} ----`)); + await etherDividendCheckpoint.methods.withdrawWithholding(dividendIndex[0]).send({from: Issuer, gas: GAS, gasPrice: DEFAULT_GAS_PRICE }) + .on('receipt', function(receipt){ + let val = receipt.events.EtherDividendWithholdingWithdrawn.returnValues._withheldAmount; + console.log(chalk.green(`\nSuccessfully withdrew ${val} ETH from checkpoint ${checkpointId} and dividend ${dividendIndex[0]} tax withholding to ${Issuer}.`)); + }); + }else{ + console.log(`\nCheckpoint doesn't exist`); + } break; case 2: return; From 55c9fe36c16085da7dded21d358a297fadd0ab91 Mon Sep 17 00:00:00 2001 From: Charles Plant <32653033+CPSTL@users.noreply.github.com> Date: Thu, 6 Sep 2018 11:25:12 -0400 Subject: [PATCH 10/22] CONTRIBUTING.md v.1.0 --- CONTRIBUTING.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..83de52f7c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing Guidelines + +## Do you have a question? + +[Check out our Gitter](https://gitter.im/PolymathNetwork/Lobby) + +See also frequently asked questions tagged with Polymath on Stack Exchange and Reddit. + +# Please Report bugs! + +Do not open an issue on Github if you think your discovered bug could be a security-relevant vulnerability. Please, read our security policy instead. + +Otherwise, just create a new issue in our repository and follow the template below: + +[Issue template to make things easier for you](https://github.com/PolymathNetwork/polymath-core/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) + + +# Contribute! + +If you would like to contribute to Polymath-core, please fork it, fix bugs or implement features, and propose a [pull request](https://github.com/PolymathNetwork/polymath-core/blob/master/PULL_REQUEST_TEMPLATE.md) + +Please, refer to the Coding Guide on our [Developer Portal](https://developers.polymath.network/) for more details about hacking on Polymath. + +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +# Pull Request Process + +[Template](https://github.com/PolymathNetwork/polymath-core/blob/master/PULL_REQUEST_TEMPLATE.md) + +# Code Styleguide + +The polymath-core repo follows the [Solidity style guide](https://solidity.readthedocs.io/en/v0.4.24/style-guide.html) + +# Code of Conduct + +[Community Standards](https://github.com/PolymathNetwork/polymath-core/blob/master/CODE_OF_CONDUCT.md) + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community feel safe and respected. + +# Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. From 3e4bcbff42e5c239dbe0bc638e9b2bc551e08dfd Mon Sep 17 00:00:00 2001 From: satyam Date: Mon, 10 Sep 2018 15:26:14 +0530 Subject: [PATCH 11/22] add versioning in the script --- .travis.yml | 5 +++++ scripts/docs.sh | 24 ++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index be99e251f..ca293679d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,15 @@ cache: - node_modules matrix: fast_finish: true +before_install: + - echo -ne '\n' | sudo add-apt-repository ppa:ethereum/ethereum + - sudo apt-get -y update + - sudo apt-get -y install solc before_script: - truffle version script: - npm run test + - npm run docs notifications: slack: secure: W4FZSabLrzF74f317hutolEHnlq2GBlQxU6b85L5XymrjgLEhlgE16c5Qz7Emoyt6le6PXL+sfG2ujJc3XYys/6hppgrHSAasuJnKCdQNpmMZ9BNyMs6WGkmB3enIf3K/FLXb26AQdwpQdIXuOeJUTf879u+YoiZV0eZH8d3+fsIOyovq9N6X5pKOpDM9iT8gGB4t7fie7xf51s+iUaHxyO9G7jDginZ4rBXHcU7mxCub9z+Z1H8+kCTnPWaF+KKVEXx4Z0nI3+urboD7E4OIP02LwrThQls2CppA3X0EoesTcdvj/HLErY/JvsXIFiFEEHZzB1Wi+k2TiOeLcYwEuHIVij+HPxxlJNX/j8uy01Uk8s4rd+0EhvfdKHJqUKqxH4YN2npcKfHEss7bU3y7dUinXQfYShW5ZewHdvc7pnnxBTfhvmdi64HdNrXAPq+s1rhciH7MmnU+tsm4lhrpr+FBuHzUMA9fOCr7b0SQytZEgWpiUls88gdbh3yG8TjyZxmZJGx09cwEP0q7VoH0UwFh7mIu5XmYdd5tWUhavTiO7YV8cUPn7MvwMsTltB3YBpF/fB26L7ka8zBhCsjm9prW6SVYU/dyO3m91VeZtO/zJFHRDA6Q58JGVW2rgzO39z193qC1EGRXqTie96VwAAtNg8+hRb+bI/CWDVzSPc= \ No newline at end of file diff --git a/scripts/docs.sh b/scripts/docs.sh index 6cb78ded3..cd9847715 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -8,6 +8,7 @@ DIRECTORY=polymath-developer-portal WEBSITE_DIRECTORY=versioned_docs CORE_ROUTE=$PWD + # functions that used to create the documentation create_docs() { @@ -17,7 +18,7 @@ create_docs() { # Check whether the branch is already present or not if [ "$(git branch -r | grep "origin/$latestTag" | wc -l)" -eq 1 ]; then - echo "$latesTag Branch is already present on remote" + echo "$latestTag Branch is already present on remote" exit 0 fi # Checkout and create the $latestTag branch @@ -29,19 +30,21 @@ create_docs() { echo "Creating the new docs for the version "$latestTag"" cd $WEBSITE_DIRECTORY - - # Creating the new directory with name $latestTag - mkdir $latestTag fi echo "Generating the API documentation in branch $latestTag" # Command to generate the documentation using the solidity-docgen - #npm install > /dev/null 2>&1 + migrate=$(SOLC_ARGS="openzeppelin-solidity="$CORE_ROUTE"/node_modules/openzeppelin-solidity" \ -solidity-docgen $CORE_ROUTE $CORE_ROUTE/contracts $CORE_ROUTE/polymath-developer-portal/) +solidity-docgen -x $CORE_ROUTE/contracts/external,$CORE_ROUTE/contracts/mocks $CORE_ROUTE $CORE_ROUTE/contracts $CORE_ROUTE/polymath-developer-portal/) + echo "Successfully docs are generated..." - echo "Transferring the API DOCS to $latestTag directory" - mv ../../docs/api_* $latestTag + + echo "Installing npm dependencies..." + yarn install > /dev/null 2>&1 + + echo "Gererate versioning docs..." + yarn run version $versionNo # Commit the changes echo "Commiting the new changes..." @@ -98,8 +101,9 @@ else echo "There is no version specific folders" create_docs else - echo "$(basename "$dir")" - if [ "$(basename "$dir")" == "$latestTag" ]; then + reponame=$(echo $(basename "$dir") | cut -d '-' -f2) + echo $reponame + if [ "$reponame" == "$versionNo" ]; then reject_docs fi fi From d7a6abdf7d0045e9529e6e1b6c1924373a010f3d Mon Sep 17 00:00:00 2001 From: satyam Date: Thu, 13 Sep 2018 14:08:09 +0530 Subject: [PATCH 12/22] minor fix --- scripts/docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docs.sh b/scripts/docs.sh index cd9847715..64194ea95 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -16,7 +16,7 @@ create_docs() { if [ "$(git branch | grep -w $latestTag)" == "" ]; then # Check whether the branch is already present or not - if [ "$(git branch -r | grep "origin/$latestTag" | wc -l)" -eq 1 ]; + if [ "$(git branch -r | grep "origin/$latestTag" | wc -l)" -ge 1 ]; then echo "$latestTag Branch is already present on remote" exit 0 From 78fd6a20e0cb7ddfd57633426314e198f4b9bd76 Mon Sep 17 00:00:00 2001 From: "Charles Plant St.Louis" <32653033+CPSTL@users.noreply.github.com> Date: Tue, 18 Sep 2018 14:07:40 -0400 Subject: [PATCH 13/22] Update to security vulnerability section --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83de52f7c..3dadf0761 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ See also frequently asked questions tagged with Polymath on Stack Exchange and R # Please Report bugs! -Do not open an issue on Github if you think your discovered bug could be a security-relevant vulnerability. Please, read our security policy instead. +Do not open an issue on Github if you think your discovered bug could be a security-relevant vulnerability. In the case of the discovery of high security venerabilities, pleasse email us the issue privately at team@polymath.network. Otherwise, just create a new issue in our repository and follow the template below: From 4f5f2867e6f0c1b44d0ee2ac305dabd437e30aa7 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 24 Sep 2018 18:30:24 +0100 Subject: [PATCH 14/22] Some refactoring --- .../modules/Checkpoint/DividendCheckpoint.sol | 209 ++++++++++++++++++ .../Checkpoint/ERC20DividendCheckpoint.sol | 2 +- .../Checkpoint/EtherDividendCheckpoint.sol | 78 +++---- 3 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 contracts/modules/Checkpoint/DividendCheckpoint.sol diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol new file mode 100644 index 000000000..c97c209f5 --- /dev/null +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -0,0 +1,209 @@ +pragma solidity ^0.4.24; + +import "./ICheckpoint.sol"; +import "../Module.sol"; +import "../../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/math/Math.sol"; + +/** + * @title Checkpoint module for issuing ether dividends + * @dev abstract contract + */ +contract DividendCheckpoint is ICheckpoint, Module { + using SafeMath for uint256; + + uint256 public EXCLUDED_ADDRESS_LIMIT = 50; + bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; + + struct Dividend { + uint256 checkpointId; + uint256 created; // Time at which the dividend was created + uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass + uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - set to very high value to bypass + uint256 amount; // Dividend amount in WEI + uint256 claimedAmount; // Amount of dividend claimed so far + uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) + bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend + uint256 dividendWithheld; + uint256 dividendWithheldReclaimed; + mapping (address => bool) claimed; // List of addresses which have claimed dividend + mapping (address => bool) excluded; // List of addresses which cannot claim dividends + } + + // List of all dividends + Dividend[] public dividends; + + // Mapping from address to withholding tax as a percentage * 10**16 + mapping (address => uint256) public withholdingTax; + + // Total amount of ETH withheld per investor + mapping (address => uint256) public investorWithheld; + + modifier validDividendIndex(uint256 _dividendIndex) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); + require(now < dividends[_dividendIndex].expiry, "Dividend expiry is in the past"); + require(!dividends[_dividendIndex].reclaimed, "Dividend has been reclaimed by issuer"); + _; + } + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) public + Module(_securityToken, _polyAddress) + { + } + + /** + * @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); + } + + /** + * @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); + for (uint256 i = 0; i < _investors.length; i++) { + require(_withholding[i] <= 10**18); + withholdingTax[_investors[i]] = _withholding[i]; + } + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors addresses of investor + * @param _withholding withholding tax for all investors (multiplied by 10**16) + */ + function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { + require(_withholding <= 10**18); + for (uint256 i = 0; i < _investors.length; i++) { + withholdingTax[_investors[i]] = _withholding; + } + } + + /** + * @notice Issuer can push dividends to provided addresses + * @param _dividendIndex Dividend to push + * @param _payees Addresses to which to push the dividend + */ + function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { + Dividend storage dividend = dividends[_dividendIndex]; + for (uint256 i = 0; i < _payees.length; i++) { + if ((!dividend.claimed[_payees[i]]) && (!dividend.excluded[_payees[i]])) { + _payDividend(_payees[i], dividend, _dividendIndex); + } + } + } + + /** + * @notice Issuer can push dividends using the investor list from the security token + * @param _dividendIndex Dividend to push + * @param _start Index in investor list at which to start pushing dividends + * @param _iterations Number of addresses to push dividends for + */ + function pushDividendPayment(uint256 _dividendIndex, uint256 _start, uint256 _iterations) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { + Dividend storage dividend = dividends[_dividendIndex]; + uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); + for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { + address payee = ISecurityToken(securityToken).investors(i); + if ((!dividend.claimed[payee]) && (!dividend.excluded[payee])) { + _payDividend(payee, dividend, _dividendIndex); + } + } + } + + /** + * @notice Investors can pull their own dividends + * @param _dividendIndex Dividend to pull + */ + function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) + { + Dividend storage dividend = dividends[_dividendIndex]; + require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); + require(!dividend.excluded[msg.sender], "msg.sender excluded from Dividend"); + _payDividend(msg.sender, dividend, _dividendIndex); + } + + /** + * @notice Internal function for paying dividends + * @param _payee address of investor + * @param _dividend storage with previously issued dividends + * @param _dividendIndex Dividend to pay + */ + function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal; + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external; + + /** + * @notice Calculate amount of dividends claimable + * @param _dividendIndex Dividend to calculate + * @param _payee Affected investor address + * @return unit256 + */ + function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee] || dividend.excluded[_payee]) { + return (0, 0); + } + uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); + return (claim, withheld); + } + + /** + * @notice Get the index according to the checkpoint id + * @param _checkpointId Checkpoint id to query + * @return uint256[] + */ + function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { + uint256 counter = 0; + for(uint256 i = 0; i < dividends.length; i++) { + if (dividends[i].checkpointId == _checkpointId) { + counter++; + } + } + + uint256[] memory index = new uint256[](counter); + counter = 0; + for(uint256 j = 0; j < dividends.length; j++) { + if (dividends[j].checkpointId == _checkpointId) { + index[counter] = j; + counter++; + } + } + return index; + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external; + + /** + * @notice Return the permissions flag that are associated with STO + * @return bytes32 array + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = DISTRIBUTE; + return allPermissions; + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index e3bcdf226..561f43de7 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -13,13 +13,13 @@ import "../../interfaces/IERC20.sol"; contract ERC20DividendCheckpoint is ICheckpoint, Module { using SafeMath for uint256; + uint256 public EXCLUDED_ADDRESS_LIMIT = 50; bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; struct Dividend { uint256 checkpointId; uint256 created; // Time at which the dividend was created uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass - uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - set to very high value to bypass address token; // Address of ERC20 token that dividend is denominated in uint256 amount; // Dividend amount in base token amounts uint256 claimedAmount; // Amount of dividend claimed so far diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index f7a530592..ed6e89f71 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -1,15 +1,11 @@ pragma solidity ^0.4.24; -import "./ICheckpoint.sol"; -import "../Module.sol"; -import "../../interfaces/ISecurityToken.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/math/Math.sol"; +import "./DividendCheckpoint.sol"; /** * @title Checkpoint module for issuing ether dividends */ -contract EtherDividendCheckpoint is ICheckpoint, Module { +contract EtherDividendCheckpoint is DividendCheckpoint { using SafeMath for uint256; uint256 public EXCLUDED_ADDRESS_LIMIT = 50; @@ -23,7 +19,9 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { uint256 amount; // Dividend amount in WEI uint256 claimedAmount; // Amount of dividend claimed so far uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) - bool reclaimed; + bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend + uint256 dividendWithheld; + uint256 dividendWithheldReclaimed; mapping (address => bool) claimed; // List of addresses which have claimed dividend mapping (address => bool) excluded; // List of addresses which cannot claim dividends } @@ -34,10 +32,6 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { // Mapping from address to withholding tax as a percentage * 10**16 mapping (address => uint256) public withholdingTax; - // Total amount of ETH withheld per dividend - mapping (uint256 => uint256) public dividendWithheld; - // Total amount of withheld ETH withdrawn by issuer per dividend - mapping (uint256 => uint256) public dividendWithheldReclaimed; // Total amount of ETH withheld per investor mapping (address => uint256) public investorWithheld; @@ -104,33 +98,8 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer */ function createDividend(uint256 _maturity, uint256 _expiry, address[] _excluded) payable public onlyOwner { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT); - require(_expiry > _maturity); - require(_expiry > now); - require(msg.value > 0); - uint256 dividendIndex = dividends.length; uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - uint256 currentSupply = ISecurityToken(securityToken).totalSupply(); - uint256 excludedSupply = 0; - for (uint256 i = 0; i < _excluded.length; i++) { - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); - } - dividends.push( - Dividend( - checkpointId, - now, - _maturity, - _expiry, - msg.value, - 0, - currentSupply.sub(excludedSupply), - false - ) - ); - for (uint256 j = 0; j < _excluded.length; j++) { - dividends[dividends.length - 1].excluded[_excluded[j]] = true; - } - emit EtherDividendDeposited(msg.sender, checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); + createDividendWithCheckpoint(_maturity, _expiry, checkpointId, _excluded); } /** @@ -140,10 +109,10 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { * @param _checkpointId Id of the checkpoint from which to issue dividend */ function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT); - require(_expiry > _maturity); - require(_expiry > now); - require(msg.value > 0); + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry is before maturity"); + require(_expiry > now), "Expiry is in the past"); + require(msg.value > 0, "No dividend sent"); require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); uint256 dividendIndex = dividends.length; uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); @@ -225,7 +194,7 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { uint256 claimAfterWithheld = claim.sub(withheld); if (claimAfterWithheld > 0) { if (_payee.send(claimAfterWithheld)) { - dividendWithheld[_dividendIndex] = dividendWithheld[_dividendIndex].add(withheld); + _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); investorWithheld[_payee] = investorWithheld[_payee].add(withheld); emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); } else { @@ -257,20 +226,21 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { * @return unit256 */ function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee] || dividend.excluded[_payee]) { - return (0, 0); - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); - uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); - uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); - return (claim, withheld); + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee] || dividend.excluded[_payee]) { + return (0, 0); + } + uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); + return (claim, withheld); } /** * @notice Get the index according to the checkpoint id * @param _checkpointId Checkpoint id to query - * @return uint256 + * @return uint256[] */ function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { uint256 counter = 0; @@ -296,8 +266,10 @@ contract EtherDividendCheckpoint is ICheckpoint, Module { * @param _dividendIndex Dividend to withdraw from */ function withdrawWithholding(uint256 _dividendIndex) public onlyOwner { - uint256 remainingWithheld = dividendWithheld[_dividendIndex].sub(dividendWithheldReclaimed[_dividendIndex]); - dividendWithheldReclaimed[_dividendIndex] = dividendWithheld[_dividendIndex]; + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); + dividend.dividendWithheldReclaimed = dividend.dividendWithheld; msg.sender.transfer(remainingWithheld); emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); } From 83dc32fc1e3169bf7cac608ba5e00c2944779974 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 24 Sep 2018 20:10:06 +0100 Subject: [PATCH 15/22] Fix erc20 dividend tests --- .../modules/Checkpoint/DividendCheckpoint.sol | 10 - .../Checkpoint/ERC20DividendCheckpoint.sol | 211 ++++-------------- .../Checkpoint/EtherDividendCheckpoint.sol | 175 +-------------- test/e_erc20_dividends.js | 88 ++++---- 4 files changed, 100 insertions(+), 384 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index c97c209f5..ade57e625 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -48,16 +48,6 @@ contract DividendCheckpoint is ICheckpoint, Module { _; } - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor (address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { - } - /** * @notice Init function i.e generalise function to maintain the structure of the module contract * @return bytes4 diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 561f43de7..8159cedd4 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -1,47 +1,21 @@ pragma solidity ^0.4.24; -import "./ICheckpoint.sol"; -import "../Module.sol"; -import "../../interfaces/ISecurityToken.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/math/Math.sol"; +import "./DividendCheckpoint.sol"; import "../../interfaces/IERC20.sol"; /** * @title Checkpoint module for issuing ERC20 dividends */ -contract ERC20DividendCheckpoint is ICheckpoint, Module { +contract ERC20DividendCheckpoint is DividendCheckpoint { using SafeMath for uint256; - uint256 public EXCLUDED_ADDRESS_LIMIT = 50; - bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; + // Mapping to token address for each dividend + mapping (uint256 => address) public dividendTokens; - struct Dividend { - uint256 checkpointId; - uint256 created; // Time at which the dividend was created - uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass - address token; // Address of ERC20 token that dividend is denominated in - uint256 amount; // Dividend amount in base token amounts - uint256 claimedAmount; // Amount of dividend claimed so far - uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) - bool reclaimed; - mapping (address => bool) claimed; // List of addresses which have claimed dividend - } - - // List of all dividends - Dividend[] public dividends; - - event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); - event ERC20DividendClaimed(address indexed _payee, uint256 _dividendIndex, address _token, uint256 _amount); - event ERC20DividendReclaimed(address indexed _claimer, uint256 _dividendIndex, address _token, uint256 _claimedAmount); - - modifier validDividendIndex(uint256 _dividendIndex) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); - require(now < dividends[_dividendIndex].expiry, "Dividend expiry is in the past"); - require(!dividends[_dividendIndex].reclaimed, "Dividend has been reclaimed by issuer"); - _; - } + event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address indexed _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); + event ERC20DividendClaimed(address indexed _payee, uint256 _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld); + event ERC20DividendReclaimed(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _claimedAmount); + event ERC20DividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _withheldAmount); /** * @notice Constructor @@ -53,14 +27,6 @@ contract ERC20DividendCheckpoint is ICheckpoint, Module { { } - /** - * @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); - } - /** * @notice Creates a dividend and checkpoint for the dividend * @param _maturity Time from which dividend can be paid @@ -68,29 +34,9 @@ contract ERC20DividendCheckpoint is ICheckpoint, Module { * @param _token Address of ERC20 token in which dividend is to be denominated * @param _amount Amount of specified token for dividend */ - function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount) public onlyOwner { - require(_expiry > _maturity); - require(_expiry > now); - require(_token != address(0)); - require(_amount > 0); - require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); - uint256 dividendIndex = dividends.length; + function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded) external onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - uint256 currentSupply = ISecurityToken(securityToken).totalSupply(); - dividends.push( - Dividend( - checkpointId, - now, - _maturity, - _expiry, - _token, - _amount, - 0, - currentSupply, - false - ) - ); - emit ERC20DividendDeposited(msg.sender, checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex); + createDividendWithCheckpoint(_maturity, _expiry, _token, _amount, checkpointId, _excluded); } /** @@ -101,69 +47,39 @@ contract ERC20DividendCheckpoint is ICheckpoint, Module { * @param _amount Amount of specified token for dividend * @param _checkpointId Checkpoint id from which to create dividends */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId) payable public onlyOwner { - require(_expiry > _maturity); - require(_expiry > now); - require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry is before maturity"); + require(_expiry > now, "Expiry is in the past"); + require(_amount > 0, "No dividend sent"); + require(_token != address(0), "0x not valid token"); + require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); + require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); uint256 dividendIndex = dividends.length; uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); - require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); + uint256 excludedSupply = 0; + for (uint256 i = 0; i < _excluded.length; i++) { + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); + } dividends.push( Dividend( _checkpointId, now, _maturity, _expiry, - _token, _amount, 0, - currentSupply, - false + currentSupply.sub(excludedSupply), + false, + 0, + 0 ) ); - emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex); - } - - /** - * @notice Issuer can push dividends to provided addresses - * @param _dividendIndex Dividend to push - * @param _payees Addresses to which to push the dividend - */ - function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - for (uint256 i = 0; i < _payees.length; i++) { - if (!dividend.claimed[_payees[i]]) { - _payDividend(_payees[i], dividend, _dividendIndex); - } - } - } - - /** - * @notice Issuer can push dividends using the investor list from the security token - * @param _dividendIndex Dividend to push - * @param _start Index in investor list at which to start pushing dividends - * @param _iterations Number of addresses to push dividends for - */ - function pushDividendPayment(uint256 _dividendIndex, uint256 _start, uint256 _iterations) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); - for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { - address payee = ISecurityToken(securityToken).investors(i); - if (!dividend.claimed[payee]) { - _payDividend(payee, dividend, _dividendIndex); - } + for (uint256 j = 0; j < _excluded.length; j++) { + dividends[dividends.length - 1].excluded[_excluded[j]] = true; } - } - - /** - * @notice Investors can pull their own dividends - * @param _dividendIndex Dividend to pull - */ - function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) - { - Dividend storage dividend = dividends[_dividendIndex]; - require(!dividend.claimed[msg.sender], "Dividend already reclaimed"); - _payDividend(msg.sender, dividend, _dividendIndex); + dividendTokens[dividendIndex] = _token; + emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex); } /** @@ -173,12 +89,13 @@ contract ERC20DividendCheckpoint is ICheckpoint, Module { * @param _dividendIndex Dividend to pay */ function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { - uint256 claim = calculateDividend(_dividendIndex, _payee); + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); _dividend.claimed[_payee] = true; _dividend.claimedAmount = claim.add(_dividend.claimedAmount); - if (claim > 0) { - require(IERC20(_dividend.token).transfer(_payee, claim), "Unable to transfer tokens"); - emit ERC20DividendClaimed(_payee, _dividendIndex, _dividend.token, claim); + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claim), "Unable to transfer tokens"); + emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); } } @@ -186,64 +103,28 @@ contract ERC20DividendCheckpoint is ICheckpoint, Module { * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends * @param _dividendIndex Dividend to reclaim */ - function reclaimDividend(uint256 _dividendIndex) public onlyOwner { + function reclaimDividend(uint256 _dividendIndex) external onlyOwner { require(_dividendIndex < dividends.length, "Incorrect dividend index"); require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); dividends[_dividendIndex].reclaimed = true; Dividend storage dividend = dividends[_dividendIndex]; uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - require(IERC20(dividend.token).transfer(msg.sender, remainingAmount), "Unable to transfer tokens"); - emit ERC20DividendReclaimed(msg.sender, _dividendIndex, dividend.token, remainingAmount); + require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingAmount), "Unable to transfer tokens"); + emit ERC20DividendReclaimed(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); } /** - * @notice Calculate amount of dividends claimable - * @param _dividendIndex Dividend to calculate - * @param _payee Affected investor address - * @return unit256 + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from */ - function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256) { + function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee]) { - return 0; - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividends[_dividendIndex].checkpointId); - return balance.mul(dividends[_dividendIndex].amount).div(dividends[_dividendIndex].totalSupply); - } - - /** - * @notice Get the index according to the checkpoint id - * @param _checkpointId Checkpoint id to query - * @return uint256 - */ - function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { - uint256 counter = 0; - for(uint256 i = 0; i < dividends.length; i++) { - if (dividends[i].checkpointId == _checkpointId) { - counter++; - } - } - - uint256[] memory index = new uint256[](counter); - counter = 0; - for(uint256 j = 0; j < dividends.length; j++) { - if (dividends[j].checkpointId == _checkpointId) { - index[counter] = j; - counter++; - } - } - return index; - } - - /** - * @notice Return the permissions flag that are associated with STO - * @return bytes32 array - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = DISTRIBUTE; - return allPermissions; + uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); + dividend.dividendWithheldReclaimed = dividend.dividendWithheld; + require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingWithheld), "Unable to transfer tokens"); + emit ERC20DividendWithholdingWithdrawn(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); } } diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index ed6e89f71..c9fe81846 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -8,47 +8,12 @@ import "./DividendCheckpoint.sol"; contract EtherDividendCheckpoint is DividendCheckpoint { using SafeMath for uint256; - uint256 public EXCLUDED_ADDRESS_LIMIT = 50; - bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; - - struct Dividend { - uint256 checkpointId; - uint256 created; // Time at which the dividend was created - uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass - uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - set to very high value to bypass - uint256 amount; // Dividend amount in WEI - uint256 claimedAmount; // Amount of dividend claimed so far - uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) - bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend - uint256 dividendWithheld; - uint256 dividendWithheldReclaimed; - mapping (address => bool) claimed; // List of addresses which have claimed dividend - mapping (address => bool) excluded; // List of addresses which cannot claim dividends - } - - // List of all dividends - Dividend[] public dividends; - - // Mapping from address to withholding tax as a percentage * 10**16 - mapping (address => uint256) public withholdingTax; - - // Total amount of ETH withheld per investor - mapping (address => uint256) public investorWithheld; - event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); - modifier validDividendIndex(uint256 _dividendIndex) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); - require(now < dividends[_dividendIndex].expiry, "Dividend expiry is in the past"); - require(!dividends[_dividendIndex].reclaimed, "Dividend has been reclaimed by issuer"); - _; - } - /** * @notice Constructor * @param _securityToken Address of the security token @@ -59,45 +24,12 @@ contract EtherDividendCheckpoint is DividendCheckpoint { { } - /** - * @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); - } - - /** - * @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); - for (uint256 i = 0; i < _investors.length; i++) { - require(_withholding[i] <= 10**18); - withholdingTax[_investors[i]] = _withholding[i]; - } - } - - /** - * @notice Function to set withholding tax rates for investors - * @param _investors addresses of investor - * @param _withholding withholding tax for all investors (multiplied by 10**16) - */ - function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { - require(_withholding <= 10**18); - for (uint256 i = 0; i < _investors.length; i++) { - withholdingTax[_investors[i]] = _withholding; - } - } - /** * @notice Creates a dividend and checkpoint for the dividend * @param _maturity Time from which dividend can be paid * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer */ - function createDividend(uint256 _maturity, uint256 _expiry, address[] _excluded) payable public onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry, address[] _excluded) payable external onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); createDividendWithCheckpoint(_maturity, _expiry, checkpointId, _excluded); } @@ -111,7 +43,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); require(_expiry > _maturity, "Expiry is before maturity"); - require(_expiry > now), "Expiry is in the past"); + require(_expiry > now, "Expiry is in the past"); require(msg.value > 0, "No dividend sent"); require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); uint256 dividendIndex = dividends.length; @@ -129,7 +61,9 @@ contract EtherDividendCheckpoint is DividendCheckpoint { msg.value, 0, currentSupply.sub(excludedSupply), - false + false, + 0, + 0 ) ); for (uint256 j = 0; j < _excluded.length; j++) { @@ -138,49 +72,6 @@ contract EtherDividendCheckpoint is DividendCheckpoint { emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); } - /** - * @notice Issuer can push dividends to provided addresses - * @param _dividendIndex Dividend to push - * @param _payees Addresses to which to push the dividend - */ - function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - for (uint256 i = 0; i < _payees.length; i++) { - if ((!dividend.claimed[_payees[i]]) && (!dividend.excluded[_payees[i]])) { - _payDividend(_payees[i], dividend, _dividendIndex); - } - } - } - - /** - * @notice Issuer can push dividends using the investor list from the security token - * @param _dividendIndex Dividend to push - * @param _start Index in investor list at which to start pushing dividends - * @param _iterations Number of addresses to push dividends for - */ - function pushDividendPayment(uint256 _dividendIndex, uint256 _start, uint256 _iterations) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); - for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { - address payee = ISecurityToken(securityToken).investors(i); - if ((!dividend.claimed[payee]) && (!dividend.excluded[payee])) { - _payDividend(payee, dividend, _dividendIndex); - } - } - } - - /** - * @notice Investors can pull their own dividends - * @param _dividendIndex Dividend to pull - */ - function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) - { - Dividend storage dividend = dividends[_dividendIndex]; - require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); - require(!dividend.excluded[msg.sender], "msg.sender excluded from Dividend"); - _payDividend(msg.sender, dividend, _dividendIndex); - } - /** * @notice Internal function for paying dividends * @param _payee address of investor @@ -208,7 +99,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends * @param _dividendIndex Dividend to reclaim */ - function reclaimDividend(uint256 _dividendIndex) public onlyOwner { + function reclaimDividend(uint256 _dividendIndex) external onlyOwner { require(_dividendIndex < dividends.length, "Incorrect dividend index"); require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); @@ -219,53 +110,11 @@ contract EtherDividendCheckpoint is DividendCheckpoint { emit EtherDividendReclaimed(msg.sender, _dividendIndex, remainingAmount); } - /** - * @notice Calculate amount of dividends claimable - * @param _dividendIndex Dividend to calculate - * @param _payee Affected investor address - * @return unit256 - */ - function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee] || dividend.excluded[_payee]) { - return (0, 0); - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); - uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); - uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); - return (claim, withheld); - } - - /** - * @notice Get the index according to the checkpoint id - * @param _checkpointId Checkpoint id to query - * @return uint256[] - */ - function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { - uint256 counter = 0; - for(uint256 i = 0; i < dividends.length; i++) { - if (dividends[i].checkpointId == _checkpointId) { - counter++; - } - } - - uint256[] memory index = new uint256[](counter); - counter = 0; - for(uint256 j = 0; j < dividends.length; j++) { - if (dividends[j].checkpointId == _checkpointId) { - index[counter] = j; - counter++; - } - } - return index; - } - /** * @notice Allows issuer to withdraw withheld tax * @param _dividendIndex Dividend to withdraw from */ - function withdrawWithholding(uint256 _dividendIndex) public onlyOwner { + function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { require(_dividendIndex < dividends.length, "Incorrect dividend index"); Dividend storage dividend = dividends[_dividendIndex]; uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); @@ -274,14 +123,4 @@ contract EtherDividendCheckpoint is DividendCheckpoint { emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); } - /** - * @notice Return the permissions flag that are associated with STO - * @return bytes32 array - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = DISTRIBUTE; - return allPermissions; - } - } diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 47e3334c6..0f8575dbb 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -190,8 +190,8 @@ contract('ERC20DividendCheckpoint', accounts => { I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({from: account_polymath}); let bytesProxy = encodeProxyCall([I_PolymathRegistry.address, I_STFactory.address, initRegFee, initRegFee, I_PolyToken.address, account_polymath]); await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, {from: account_polymath}); - I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); - + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + // Step 10: Deploy the FeatureRegistry I_FeatureRegistry = await FeatureRegistry.new( @@ -264,7 +264,7 @@ contract('ERC20DividendCheckpoint', accounts => { }); - it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { + it("Should successfully attach the ERC20DividendCheckpoint with the security token - fail insufficient payment", async () => { let errorThrown = false; try { const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { from: token_owner }); @@ -276,7 +276,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { + it("Should successfully attach the ERC20DividendCheckpoint with the security token with budget", async () => { 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}); @@ -360,13 +360,13 @@ contract('ERC20DividendCheckpoint', accounts => { ); }); - it("Should fail in creating the dividend", async() => { + it("Should fail in creating the dividend - incorrect allowance", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance = 0`.grey); ensureException(error); @@ -375,13 +375,13 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Should fail in creating the dividend", async() => { + it("Should fail in creating the dividend - maturity > expiry", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -390,12 +390,12 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Should fail in creating the dividend", async() => { + it("Should fail in creating the dividend - now > expiry", async() => { let errorThrown = false; let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -404,12 +404,12 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Should fail in creating the dividend", async() => { + it("Should fail in creating the dividend - bad token", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because token address is 0x`.grey); ensureException(error); @@ -418,12 +418,12 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Should fail in creating the dividend", async() => { + it("Should fail in creating the dividend - amount is 0", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because amount < 0`.grey); ensureException(error); @@ -436,7 +436,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); }); @@ -446,7 +446,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('3', 'ether')); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails maturity in the future", async() => { let errorThrown = false; try { await I_ERC20DividendCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner}); @@ -458,7 +458,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails not owner", async() => { let errorThrown = false; // Increase time by 2 day await increaseTime(duration.days(2)); @@ -472,7 +472,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails wrong index", async() => { let errorThrown = false; try { await I_ERC20DividendCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); @@ -494,7 +494,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(investor1BalanceAfter.sub(investor1Balance).toNumber(), web3.utils.toWei('0.5', 'ether')); assert.equal(investor2BalanceAfter.sub(investor2Balance).toNumber(), web3.utils.toWei('1', 'ether')); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(0))[6].toNumber(), web3.utils.toWei('1.5', 'ether')); + assert.equal((await I_ERC20DividendCheckpoint.dividends(0))[5].toNumber(), web3.utils.toWei('1.5', 'ether')); }); it("Buy some tokens for account_temp (1 ETH)", async() => { @@ -527,11 +527,11 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint - fails past expiry", async() => { let errorThrown = false; await increaseTime(duration.days(12)); try { @@ -544,7 +544,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { + it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoin - fails already reclaimed", async() => { let errorThrown = false; let tx = await I_ERC20DividendCheckpoint.reclaimDividend(1, {from: token_owner, gas: 500000}); assert.equal((tx.logs[0].args._claimedAmount).toNumber(), web3.utils.toWei("1.5", "ether")); @@ -588,7 +588,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), [], {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); }); @@ -644,7 +644,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('3', 'ether')); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(2))[6].toNumber(), web3.utils.toWei('11', 'ether')); + assert.equal((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); }); it("Investor 2 transfers 1 ETH of his token balance to investor 1", async() => { @@ -654,14 +654,16 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit - fails bad allowance", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(2); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); + console.log(JSON.stringify(tx.logs[0].args)); + console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); await I_PolyToken.getTokens(web3.utils.toWei('20', 'ether'), token_owner); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance is not provided`.grey); ensureException(error); @@ -670,13 +672,15 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit - fails maturity > expiry", async() => { + console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); + let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('20', 'ether'), {from: token_owner}); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -685,12 +689,14 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit - fails now > expiry", async() => { + console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); + let errorThrown = false; let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -699,12 +705,12 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit - fails bad checkpoint", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, [], {from: token_owner}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -716,10 +722,9 @@ contract('ERC20DividendCheckpoint', accounts => { it("Create another new dividend with explicit", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); - let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), 4, {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), 4, [], {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); }); @@ -755,13 +760,14 @@ contract('ERC20DividendCheckpoint', accounts => { it("should calculate dividend before the push dividend payment", async() => { let dividendAmount1 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor1); + console.log(JSON.stringify(dividendAmount1)); let dividendAmount2 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor2); let dividendAmount3 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor3); let dividendAmount_temp = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_temp); - assert.equal(dividendAmount1.toNumber(), web3.utils.toWei("1", "ether")); - assert.equal(dividendAmount2.toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(dividendAmount3.toNumber(), web3.utils.toWei("7", "ether")); - assert.equal(dividendAmount_temp.toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); + assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); }); it("Investor 2 claims dividend", async() => { @@ -795,14 +801,14 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[6].toNumber(), web3.utils.toWei('4', 'ether')); + assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); }); it("should calculate dividend after the push dividend payment", async() => { let dividendAmount1 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor1); let dividendAmount2 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor2); - assert.equal(dividendAmount1.toNumber(), 0); - assert.equal(dividendAmount2.toNumber(), 0); + assert.equal(dividendAmount1[0].toNumber(), 0); + assert.equal(dividendAmount2[0].toNumber(), 0); }); it("Issuer unable to reclaim dividend (expiry not passed)", async() => { From 134fd567ac77a414f194374406f57437dd817507 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 25 Sep 2018 09:43:39 +0100 Subject: [PATCH 16/22] Update Change Log --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdc35af8..48c7ab71d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file. [__1.5.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __15-08-18__ ## Added -* `transferTickerOwnership()` function is introduced in `TickerRegistry` to transfer the ticker ownership after the registeration #191. +* Added withholding tax to ether & erc20 dividends +* `transferTickerOwnership()` function is introduced in `TickerRegistry` to transfer the ticker ownership after the registeration #191. * `getTickersByOwner()` function is used to get the list of tickers owned by the issuer #189. * New function `addCustomTicker()` is used the add the Ticker in tickerRegistry. To avail the facility to Registry owner to add the tokens without paying the fee #190. * Adding the functionality to change the `version`,`name`,`description`,`title` of a Module factory. @@ -18,10 +19,10 @@ All notable changes to this project will be documented in this file. * Add new function `modifyTickerDetails()`, To modify the details of undeployed ticker. #230 ## Fixed -* Generalize the STO varaible names and added them in `ISTO.sol` to use the common standard in all STOs. +* Generalize the STO varaible names and added them in `ISTO.sol` to use the common standard in all STOs. * Generalize the event when any new token get registered with the polymath ecosystem. `LogNewSecurityToken` should emit _ticker, _name, _securityTokenAddress, _owner, _addedAt, _registrant respectively. #230 - -## Removed + +## Removed * Remove `swarmHash` from the `registerTicker(), addCustomTicker(), generateSecurityToken(), addCustomSecurityToken()` functions of TickerRegistry.sol and SecurityTokenRegistry.sol. #230 ====== @@ -80,7 +81,7 @@ allowed) * Minor CLI fixes * Change in the datastructure of SymbolDetails new variable `expiredTimestamp` introduced and change the variable name `timestamp` to `registeredTimestamp` in Tickerregistry.sol #192. * Rounding edge cases in USDTieredSTO.sol that could have reverted valid transactions -* Bug in ManualApprovalTransferManager that allowed anyone to reduce anyone's transfer allowance +* Bug in ManualApprovalTransferManager that allowed anyone to reduce anyone's transfer allowance ======= # v1.3.0 From d5ab479df15e00fd20e921804319510db7c63474 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 25 Sep 2018 15:48:32 +0100 Subject: [PATCH 17/22] Updates --- .../modules/Checkpoint/DividendCheckpoint.sol | 27 +++++++++++---- .../Checkpoint/ERC20DividendCheckpoint.sol | 33 ++++++++++++++++--- .../Checkpoint/EtherDividendCheckpoint.sol | 33 +++++++++++++++---- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index ade57e625..a4af1fb4a 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -28,18 +28,23 @@ contract DividendCheckpoint is ICheckpoint, Module { uint256 dividendWithheld; uint256 dividendWithheldReclaimed; mapping (address => bool) claimed; // List of addresses which have claimed dividend - mapping (address => bool) excluded; // List of addresses which cannot claim dividends + mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends } // List of all dividends Dividend[] public dividends; + // List of addresses which cannot claim dividends + address[] public excluded; + // Mapping from address to withholding tax as a percentage * 10**16 mapping (address => uint256) public withholdingTax; // Total amount of ETH withheld per investor mapping (address => uint256) public investorWithheld; + event SetExcludedAddresses(address[] _excluded, uint256 _timestamp); + modifier validDividendIndex(uint256 _dividendIndex) { require(_dividendIndex < dividends.length, "Incorrect dividend index"); require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); @@ -62,13 +67,23 @@ contract DividendCheckpoint is ICheckpoint, Module { * @param _withholding withholding tax for individual investors (multiplied by 10**16) */ function setWithholding(address[] _investors, uint256[] _withholding) public onlyOwner { - require(_investors.length == _withholding.length); + 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]; } } + /** + * @notice Function to clear and set list of excluded addresses used for future dividends + * @param _investors addresses of investor + */ + function setExcluded(address[] _excluded) public onlyOwner { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); + excluded = _excluded; + emit SetExcludedAddresses(excluded, now); + } + /** * @notice Function to set withholding tax rates for investors * @param _investors addresses of investor @@ -89,7 +104,7 @@ contract DividendCheckpoint is ICheckpoint, Module { function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { Dividend storage dividend = dividends[_dividendIndex]; for (uint256 i = 0; i < _payees.length; i++) { - if ((!dividend.claimed[_payees[i]]) && (!dividend.excluded[_payees[i]])) { + if ((!dividend.claimed[_payees[i]]) && (!dividend.dividendExcluded[_payees[i]])) { _payDividend(_payees[i], dividend, _dividendIndex); } } @@ -106,7 +121,7 @@ contract DividendCheckpoint is ICheckpoint, Module { uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { address payee = ISecurityToken(securityToken).investors(i); - if ((!dividend.claimed[payee]) && (!dividend.excluded[payee])) { + if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) { _payDividend(payee, dividend, _dividendIndex); } } @@ -120,7 +135,7 @@ contract DividendCheckpoint is ICheckpoint, Module { { Dividend storage dividend = dividends[_dividendIndex]; require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); - require(!dividend.excluded[msg.sender], "msg.sender excluded from Dividend"); + require(!dividend.dividendExcluded[msg.sender], "msg.sender excluded from Dividend"); _payDividend(msg.sender, dividend, _dividendIndex); } @@ -147,7 +162,7 @@ contract DividendCheckpoint is ICheckpoint, Module { function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { require(_dividendIndex < dividends.length, "Incorrect dividend index"); Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee] || dividend.excluded[_payee]) { + if (dividend.claimed[_payee] || dividend.dividendExcluded[_payee]) { return (0, 0); } uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 8159cedd4..ed8eec7ae 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -34,9 +34,33 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _token Address of ERC20 token in which dividend is to be denominated * @param _amount Amount of specified token for dividend */ - function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded) external onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount) external onlyOwner { + createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded); + } + + /** + * @notice Creates a dividend with a provided checkpoint + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _checkpointId Checkpoint id from which to create dividends + */ + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId) external onlyOwner { + createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded); + } + + /** + * @notice Creates a dividend and checkpoint for the dividend + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _excluded List of addresses to exclude + */ + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded) public onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - createDividendWithCheckpoint(_maturity, _expiry, _token, _amount, checkpointId, _excluded); + createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded); } /** @@ -46,8 +70,9 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _token Address of ERC20 token in which dividend is to be denominated * @param _amount Amount of specified token for dividend * @param _checkpointId Checkpoint id from which to create dividends + * @param _excluded List of addresses to exclude */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { + function createDividendWithCheckpointAndExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, address[] _excluded) public onlyOwner { require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); require(_expiry > _maturity, "Expiry is before maturity"); require(_expiry > now, "Expiry is in the past"); @@ -76,7 +101,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { ) ); for (uint256 j = 0; j < _excluded.length; j++) { - dividends[dividends.length - 1].excluded[_excluded[j]] = true; + dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; } dividendTokens[dividendIndex] = _token; emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex); diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index c9fe81846..e4f10172a 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -25,22 +25,43 @@ contract EtherDividendCheckpoint is DividendCheckpoint { } /** - * @notice Creates a dividend and checkpoint for the dividend + * @notice Creates a dividend and checkpoint for the dividend, using global list of excluded addresses * @param _maturity Time from which dividend can be paid * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer */ - function createDividend(uint256 _maturity, uint256 _expiry, address[] _excluded) payable external onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry) payable external onlyOwner { + createDividendWithExclusions(_maturity, _expiry, excluded); + } + + /** + * @notice Creates a dividend with a provided checkpoint, using global list of excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _checkpointId Id of the checkpoint from which to issue dividend + */ + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId) payable external onlyOwner { + createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded); + } + + /** + * @notice Creates a dividend and checkpoint for the dividend, specifying explicit excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _excluded List of addresses to exclude + */ + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address[] _excluded) payable public onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - createDividendWithCheckpoint(_maturity, _expiry, checkpointId, _excluded); + createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded); } /** - * @notice Creates a dividend with a provided checkpoint + * @notice Creates a dividend with a provided checkpoint, specifying explicit excluded addresses * @param _maturity Time from which dividend can be paid * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer * @param _checkpointId Id of the checkpoint from which to issue dividend + * @param _excluded List of addresses to exclude */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { + function createDividendWithCheckpointAndExclusions(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); require(_expiry > _maturity, "Expiry is before maturity"); require(_expiry > now, "Expiry is in the past"); @@ -67,7 +88,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { ) ); for (uint256 j = 0; j < _excluded.length; j++) { - dividends[dividends.length - 1].excluded[_excluded[j]] = true; + dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; } emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); } From 1bf3733518747299ce0b497ecd33cf6e91eeeaab Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 25 Sep 2018 18:22:53 +0100 Subject: [PATCH 18/22] Fixes and more tests --- .../modules/Checkpoint/DividendCheckpoint.sol | 2 +- test/e_erc20_dividends.js | 66 +++++++++++-------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index a4af1fb4a..e427509ab 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -76,7 +76,7 @@ contract DividendCheckpoint is ICheckpoint, Module { /** * @notice Function to clear and set list of excluded addresses used for future dividends - * @param _investors addresses of investor + * @param _excluded addresses of investor */ function setExcluded(address[] _excluded) public onlyOwner { require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 0f8575dbb..0e0f1909b 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -366,7 +366,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance = 0`.grey); ensureException(error); @@ -381,7 +381,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -395,7 +395,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -409,7 +409,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); } catch(error) { console.log(` tx -> failed because token address is 0x`.grey); ensureException(error); @@ -423,7 +423,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, {from: token_owner}); } catch(error) { console.log(` tx -> failed because amount < 0`.grey); ensureException(error); @@ -436,7 +436,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); }); @@ -485,7 +485,6 @@ contract('ERC20DividendCheckpoint', accounts => { }); it("Issuer pushes dividends iterating over account holders - dividends proportional to checkpoint", async() => { - let _dev = await I_ERC20DividendCheckpoint.dividends.call(0); let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); await I_ERC20DividendCheckpoint.pushDividendPayment(0, 0, 10, {from: token_owner, gas: 5000000}); @@ -527,7 +526,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); @@ -583,16 +582,20 @@ contract('ERC20DividendCheckpoint', accounts => { ); }); + it("Exclude account_temp using global exclusion list", async() => { + await I_ERC20DividendCheckpoint.setExcluded([account_temp], {from: token_owner}); + }); + it("Create another new dividend", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); }); - it("should investor 3 claims dividend", async() => { + it("should investor 3 claims dividend - fail bad index", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -608,6 +611,7 @@ contract('ERC20DividendCheckpoint', accounts => { }); it("should investor 3 claims dividend", async() => { + console.log((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber()); let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); @@ -620,7 +624,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), web3.utils.toWei('7', 'ether')); }); - it("should investor 3 claims dividend", async() => { + it("should investor 3 claims dividend - fails already claimed", async() => { let errorThrown = false; try { await I_ERC20DividendCheckpoint.pullDividendPayment(2, {from: account_investor3, gasPrice: 0}); @@ -633,28 +637,39 @@ contract('ERC20DividendCheckpoint', accounts => { }); it("should issuer pushes remain", async() => { + console.log((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber()); let investor1BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investorTempBalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_temp)); await I_ERC20DividendCheckpoint.pushDividendPayment(2, 0, 10, {from: token_owner}); let investor1BalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2BalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_investor3)); + let investorTempBalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_temp)); assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei('3', 'ether')); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); + assert.equal(investorTempBalanceAfter2.sub(investorTempBalanceAfter1).toNumber(), 0); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('11', 'ether')); + assert.equal((await I_ERC20DividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei('10', 'ether')); }); + + it("Delete global exclusion list", async() => { + await I_ERC20DividendCheckpoint.setExcluded([], {from: token_owner}); + }); + + it("Investor 2 transfers 1 ETH of his token balance to investor 1", async() => { await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), {from: account_investor2}); assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('2', 'ether')); assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_temp), web3.utils.toWei('1', 'ether')); }); - it("Create another new dividend with explicit - fails bad allowance", async() => { + it("Create another new dividend with explicit checkpoint - fails bad allowance", async() => { let errorThrown = false; let maturity = latestTime(); let expiry = latestTime() + duration.days(2); @@ -663,7 +678,7 @@ contract('ERC20DividendCheckpoint', accounts => { console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); await I_PolyToken.getTokens(web3.utils.toWei('20', 'ether'), token_owner); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance is not provided`.grey); ensureException(error); @@ -680,7 +695,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('20', 'ether'), {from: token_owner}); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -696,7 +711,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, [], {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -710,7 +725,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, [], {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, {from: token_owner}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -719,16 +734,16 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create another new dividend with explicit", async() => { + it("Create another new dividend with explicit checkpoint and exclusion", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('11', 'ether'), 4, [], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), 4, [account_investor1], {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); }); - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + it("Investor 2 claims dividend, issuer pushes investor 1 - fails not owner", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -743,7 +758,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Investor 2 claims dividend, issuer pushes investor 1", async() => { + it("Investor 2 claims dividend, issuer pushes investor 1 - fails bad index", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -760,11 +775,10 @@ contract('ERC20DividendCheckpoint', accounts => { it("should calculate dividend before the push dividend payment", async() => { let dividendAmount1 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor1); - console.log(JSON.stringify(dividendAmount1)); let dividendAmount2 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor2); let dividendAmount3 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor3); let dividendAmount_temp = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_temp); - assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); @@ -786,7 +800,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); }); - it("Should issuer pushes investor 1 and temp investor", async() => { + it("Should issuer pushes temp investor - investor1 excluded", async() => { let investor1BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor3)); @@ -796,12 +810,12 @@ contract('ERC20DividendCheckpoint', accounts => { let investor2BalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_investor3)); let tempBalanceAfter2 = BigNumber(await I_PolyToken.balanceOf(account_temp)); - assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); //Check fully claimed - assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('4', 'ether')); + assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('3', 'ether')); }); it("should calculate dividend after the push dividend payment", async() => { From 7ea9246cecd3a5d3d8477f40f5788764bd84e130 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 25 Sep 2018 19:30:57 +0100 Subject: [PATCH 19/22] More fixes --- .../Checkpoint/ERC20DividendCheckpoint.sol | 4 +++- test/e_erc20_dividends.js | 21 +++++++++++++++--- test/f_ether_dividends.js | 22 +++++++++---------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index ed8eec7ae..8a352031d 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -119,7 +119,9 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { _dividend.claimedAmount = claim.add(_dividend.claimedAmount); uint256 claimAfterWithheld = claim.sub(withheld); if (claimAfterWithheld > 0) { - require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claim), "Unable to transfer tokens"); + require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "Unable to transfer tokens"); + _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); + investorWithheld[_payee] = investorWithheld[_payee].add(withheld); emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); } } diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 0e0f1909b..4e4812419 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -734,6 +734,10 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); + it("Set withholding tax of 20% on account_temp and 10% on investor2", async() => { + await I_ERC20DividendCheckpoint.setWithholding([account_temp, account_investor2], [BigNumber(20*10**16), BigNumber(10*10**16)], {from: token_owner}); + }); + it("Create another new dividend with explicit checkpoint and exclusion", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); @@ -782,6 +786,10 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); + assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("0.2", "ether")); + assert.equal(dividendAmount3[1].toNumber(), web3.utils.toWei("0", "ether")); + assert.equal(dividendAmount_temp[1].toNumber(), web3.utils.toWei("0.2", "ether")); }); it("Investor 2 claims dividend", async() => { @@ -795,7 +803,7 @@ contract('ERC20DividendCheckpoint', accounts => { let investor3BalanceAfter1 = BigNumber(await I_PolyToken.balanceOf(account_investor3)); let tempBalanceAfter1 = BigNumber(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('2', 'ether')); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei('1.8', 'ether')); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); }); @@ -813,7 +821,7 @@ contract('ERC20DividendCheckpoint', accounts => { assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); - assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(tempBalanceAfter2.sub(tempBalanceAfter1).toNumber(), web3.utils.toWei('0.8', 'ether')); //Check fully claimed assert.equal((await I_ERC20DividendCheckpoint.dividends(3))[5].toNumber(), web3.utils.toWei('3', 'ether')); }); @@ -823,7 +831,14 @@ contract('ERC20DividendCheckpoint', accounts => { let dividendAmount2 = await I_ERC20DividendCheckpoint.calculateDividend.call(3, account_investor2); assert.equal(dividendAmount1[0].toNumber(), 0); assert.equal(dividendAmount2[0].toNumber(), 0); - }); + }); + + it("Issuer reclaims withholding tax", async() => { + let issuerBalance = BigNumber(await I_PolyToken.balanceOf(token_owner)); + await I_ERC20DividendCheckpoint.withdrawWithholding(3, {from: token_owner, gasPrice: 0}); + let issuerBalanceAfter = BigNumber(await I_PolyToken.balanceOf(token_owner)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei('0.4', 'ether')) + }); it("Issuer unable to reclaim dividend (expiry not passed)", async() => { let errorThrown = false; diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 2d4530cdd..474e0893d 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -365,7 +365,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner}); } catch(error) { console.log(` tx -> failed because msg.value = 0`.grey); ensureException(error); @@ -379,7 +379,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -393,7 +393,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -409,7 +409,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); }); @@ -511,7 +511,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); @@ -577,7 +577,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create another new dividend", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); }); @@ -663,7 +663,7 @@ contract('EtherDividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(2); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: 0}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: 0}); } catch(error) { console.log(` tx -> failed because msg.value is 0`.grey); ensureException(error); @@ -677,7 +677,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -691,7 +691,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -705,7 +705,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, [], {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -718,7 +718,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, [account_investor1], {from: token_owner, value: web3.utils.toWei('10', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, [account_investor1], {from: token_owner, value: web3.utils.toWei('10', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); }); From e6c4926465d23a6563e8a676887c97c5292d2a09 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Wed, 26 Sep 2018 09:49:31 +0100 Subject: [PATCH 20/22] Get correct balances for historical checkpoints --- contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol | 2 +- contracts/modules/Checkpoint/EtherDividendCheckpoint.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 8a352031d..ad61b24a8 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -84,7 +84,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); uint256 excludedSupply = 0; for (uint256 i = 0; i < _excluded.length; i++) { - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); } dividends.push( Dividend( diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index e4f10172a..8fd589d5e 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -71,7 +71,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); uint256 excludedSupply = 0; for (uint256 i = 0; i < _excluded.length; i++) { - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOf(_excluded[i])); + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); } dividends.push( Dividend( From bf5116d862a615b3941cd466bed8ec4a9c9bb281 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Wed, 26 Sep 2018 09:55:05 +0100 Subject: [PATCH 21/22] Revert claimedAmount on failed send --- contracts/modules/Checkpoint/EtherDividendCheckpoint.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 8fd589d5e..ba9cddf4b 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -102,7 +102,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); _dividend.claimed[_payee] = true; - _dividend.claimedAmount = claim.add(_dividend.claimedAmount); + _dividend.claimedAmount = _dividend.claimedAmount.add(claim); uint256 claimAfterWithheld = claim.sub(withheld); if (claimAfterWithheld > 0) { if (_payee.send(claimAfterWithheld)) { @@ -111,6 +111,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); } else { _dividend.claimed[_payee] = false; + _dividend.claimedAmount = _dividend.claimedAmount.sub(claim); emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); } } From bb69c8029c52f07b14bed8aa43c8f64a00088cd2 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Wed, 26 Sep 2018 10:32:53 +0100 Subject: [PATCH 22/22] Add test case for failed ETH send --- .../Checkpoint/EtherDividendCheckpoint.sol | 5 +- test/f_ether_dividends.js | 64 +++++++++++++++++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index ba9cddf4b..1d17e1abb 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -101,17 +101,16 @@ contract EtherDividendCheckpoint is DividendCheckpoint { */ function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - _dividend.claimedAmount = _dividend.claimedAmount.add(claim); + _dividend.claimed[_payee] = true; uint256 claimAfterWithheld = claim.sub(withheld); if (claimAfterWithheld > 0) { if (_payee.send(claimAfterWithheld)) { + _dividend.claimedAmount = _dividend.claimedAmount.add(claim); _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); investorWithheld[_payee] = investorWithheld[_payee].add(withheld); emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); } else { _dividend.claimed[_payee] = false; - _dividend.claimedAmount = _dividend.claimedAmount.sub(claim); emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); } } diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 474e0893d..4d58e26c8 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -512,7 +512,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 2"); }); it("Issuer pushes dividends fails due to passed expiry", async() => { @@ -578,10 +578,10 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 3"); }); - it("should investor 3 claims dividend", async() => { + it("should investor 3 claims dividend - fails bad index", async() => { let errorThrown = false; let investor1Balance = BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); @@ -719,7 +719,7 @@ contract('EtherDividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, [account_investor1], {from: token_owner, value: web3.utils.toWei('10', 'ether')}); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 4"); }); it("Non-owner pushes investor 1 - fails", async() => { @@ -728,7 +728,7 @@ contract('EtherDividendCheckpoint', accounts => { let investor2Balance = BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3Balance = BigNumber(await I_PolyToken.balanceOf(account_investor3)); try { - await I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(4, [account_investor2, account_investor1],{from: account_investor2, gasPrice: 0}); + await I_EtherDividendCheckpoint.pushDividendPaymentToAddresses(3, [account_investor2, account_investor1],{from: account_investor2, gasPrice: 0}); } catch(error) { console.log(` tx -> failed because not called by the owner`.grey); ensureException(error); @@ -863,7 +863,61 @@ contract('EtherDividendCheckpoint', accounts => { ensureException(error); } assert.ok(errorThrown, message); + }); + + it("Assign token balance to an address that can't receive funds", async() => { + + let tx = await I_GeneralTransferManager.modifyWhitelist( + I_PolyToken.address, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + }); + // Jump time + await increaseTime(5000); + // Mint some tokens + await I_SecurityToken.mint(I_PolyToken.address, web3.utils.toWei('1', 'ether'), { from: token_owner }); + assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('2', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_investor3), web3.utils.toWei('7', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(account_temp), web3.utils.toWei('1', 'ether')); + assert.equal(await I_SecurityToken.balanceOf(I_PolyToken.address), web3.utils.toWei('1', 'ether')); + }); + + it("Create another new dividend", async() => { + let maturity = latestTime(); + let expiry = latestTime() + duration.days(10); + let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('12', 'ether')}); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 6, "Dividend should be created at checkpoint 6"); + }); + + it("Should issuer pushes all dividends", async() => { + let investor1BalanceBefore = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceBefore = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceBefore = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalanceBefore = BigNumber(await web3.eth.getBalance(account_temp)); + let tokenBalanceBefore = BigNumber(await web3.eth.getBalance(I_PolyToken.address)); + await I_EtherDividendCheckpoint.pushDividendPayment(4, 0, 10, {from: token_owner}); + + let investor1BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor1)); + let investor2BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor2)); + let investor3BalanceAfter = BigNumber(await web3.eth.getBalance(account_investor3)); + let tempBalanceAfter = BigNumber(await web3.eth.getBalance(account_temp)); + let tokenBalanceAfter = BigNumber(await web3.eth.getBalance(I_PolyToken.address)); + + assert.equal(investor1BalanceAfter.sub(investor1BalanceBefore).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(investor2BalanceAfter.sub(investor2BalanceBefore).toNumber(), web3.utils.toWei('1.6', 'ether')); + assert.equal(investor3BalanceAfter.sub(investor3BalanceBefore).toNumber(), web3.utils.toWei('7', 'ether')); + assert.equal(tempBalanceAfter.sub(tempBalanceBefore).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal(tokenBalanceAfter.sub(tokenBalanceBefore).toNumber(), web3.utils.toWei('0', 'ether')); + + //Check partially claimed + assert.equal((await I_EtherDividendCheckpoint.dividends(4))[5].toNumber(), web3.utils.toWei('11', 'ether')); }); it("Should give the right dividend index", async() => {